Showing preview only (4,987K chars total). Download the full file or copy to clipboard to get everything.
Repository: pushpalroy/jetlime
Branch: main
Commit: 0fcfa7f366d3
Files: 262
Total size: 4.7 MB
Directory structure:
gitextract_33wmcawt/
├── .claude/
│ ├── commands/
│ │ ├── bump-version.md
│ │ ├── ci-local.md
│ │ ├── dokka.md
│ │ └── spotless.md
│ └── skills/
│ ├── compose-expert/
│ │ ├── SKILL.md
│ │ └── references/
│ │ ├── accessibility.md
│ │ ├── animation.md
│ │ ├── atomic-design.md
│ │ ├── auto-init.md
│ │ ├── composition-locals.md
│ │ ├── deprecated-patterns.md
│ │ ├── design-to-compose.md
│ │ ├── lists-scrolling.md
│ │ ├── material3-motion.md
│ │ ├── modifiers.md
│ │ ├── multiplatform.md
│ │ ├── navigation.md
│ │ ├── performance.md
│ │ ├── platform-specifics.md
│ │ ├── pr-review.md
│ │ ├── production-crash-playbook.md
│ │ ├── side-effects.md
│ │ ├── source-code/
│ │ │ ├── cmp-source.md
│ │ │ ├── foundation-source.md
│ │ │ ├── material3-source.md
│ │ │ ├── navigation-source.md
│ │ │ ├── runtime-source.md
│ │ │ └── ui-source.md
│ │ ├── state-management.md
│ │ ├── styles-experimental.md
│ │ ├── theming-material3.md
│ │ ├── tv-compose.md
│ │ └── view-composition.md
│ ├── edge-to-edge/
│ │ └── SKILL.md
│ └── r8-analyzer/
│ ├── SKILL.md
│ └── references/
│ ├── CONFIGURATION.md
│ ├── KEEP-RULES-IMPACT-HIERARCHY.md
│ ├── REDUNDANT-RULES.md
│ ├── REFLECTION-GUIDE.md
│ └── android/
│ └── topic/
│ └── performance/
│ └── app-optimization/
│ └── enable-app-optimization.md
├── .editorconfig
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── publish.yml
│ └── setup/
│ ├── ios-setup/
│ │ └── action.yml
│ └── java-setup/
│ └── action.yml
├── .gitignore
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── PUBLISHING.md
├── README.md
├── build.gradle.kts
├── docs/
│ ├── index.html
│ ├── jetlime/
│ │ ├── com.pushpal.jetlime/
│ │ │ ├── -event-point-animation/
│ │ │ │ ├── animation-spec.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── initial-value.html
│ │ │ │ └── target-value.html
│ │ │ ├── -event-point-type/
│ │ │ │ ├── -companion/
│ │ │ │ │ ├── -default.html
│ │ │ │ │ ├── -e-m-p-t-y.html
│ │ │ │ │ ├── custom.html
│ │ │ │ │ ├── filled.html
│ │ │ │ │ └── index.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── fill-percent.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── icon.html
│ │ │ │ ├── index.html
│ │ │ │ ├── is-custom.html
│ │ │ │ ├── is-empty-or-filled.html
│ │ │ │ ├── is-filled.html
│ │ │ │ ├── tint.html
│ │ │ │ └── type.html
│ │ │ ├── -event-position/
│ │ │ │ ├── -companion/
│ │ │ │ │ ├── dynamic.html
│ │ │ │ │ └── index.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── is-not-end.html
│ │ │ │ ├── is-not-start.html
│ │ │ │ └── name.html
│ │ │ ├── -horizontal-alignment/
│ │ │ │ ├── -b-o-t-t-o-m/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -t-o-p/
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ ├── -items-list/
│ │ │ │ ├── -items-list.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ └── items.html
│ │ │ ├── -jet-lime-column.html
│ │ │ ├── -jet-lime-defaults/
│ │ │ │ ├── column-style.html
│ │ │ │ ├── index.html
│ │ │ │ ├── line-gradient-brush.html
│ │ │ │ ├── line-solid-brush.html
│ │ │ │ └── row-style.html
│ │ │ ├── -jet-lime-event-defaults/
│ │ │ │ ├── event-style.html
│ │ │ │ ├── index.html
│ │ │ │ └── point-animation.html
│ │ │ ├── -jet-lime-event-style/
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── point-animation.html
│ │ │ │ ├── point-color.html
│ │ │ │ ├── point-fill-color.html
│ │ │ │ ├── point-placement.html
│ │ │ │ ├── point-radius.html
│ │ │ │ ├── point-stroke-color.html
│ │ │ │ ├── point-stroke-width.html
│ │ │ │ ├── point-type.html
│ │ │ │ ├── position.html
│ │ │ │ ├── set-point-placement.html
│ │ │ │ └── set-position.html
│ │ │ ├── -jet-lime-event.html
│ │ │ ├── -jet-lime-extended-event.html
│ │ │ ├── -jet-lime-row.html
│ │ │ ├── -jet-lime-style/
│ │ │ │ ├── content-distance.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── item-spacing.html
│ │ │ │ ├── line-brush.html
│ │ │ │ ├── line-horizontal-alignment.html
│ │ │ │ ├── line-thickness.html
│ │ │ │ ├── line-vertical-alignment.html
│ │ │ │ └── path-effect.html
│ │ │ ├── -local-jet-lime-style.html
│ │ │ ├── -point-placement/
│ │ │ │ ├── -c-e-n-t-e-r/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -e-n-d/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -s-t-a-r-t/
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ ├── -vertical-alignment/
│ │ │ │ ├── -l-e-f-t/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -r-i-g-h-t/
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ └── index.html
│ │ └── package-list
│ ├── navigation.html
│ ├── scripts/
│ │ ├── main.js
│ │ ├── navigation-loader.js
│ │ ├── pages.json
│ │ ├── platform-content-handler.js
│ │ ├── prism.js
│ │ ├── safe-local-storage_blocking.js
│ │ └── sourceset_dependencies.js
│ └── styles/
│ ├── logo-styles.css
│ ├── main.css
│ ├── prism.css
│ └── style.css
├── dokkaModule.md
├── dokkaPackage.md
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── jetlime/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── dokkaModule.md
│ ├── dokkaPackage.md
│ ├── jetlime.podspec
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidMain/
│ │ └── AndroidManifest.xml
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── pushpal/
│ │ └── jetlime/
│ │ ├── ExampleInstrumentedTest.kt
│ │ ├── JetLimeColumnTest.kt
│ │ └── JetLimeRowTest.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── pushpal/
│ │ └── jetlime/
│ │ ├── EventPointAnimation.kt
│ │ ├── EventPointType.kt
│ │ ├── EventPosition.kt
│ │ ├── ItemsList.kt
│ │ ├── JetLimeDefaults.kt
│ │ ├── JetLimeEvent.kt
│ │ ├── JetLimeEventDefaults.kt
│ │ ├── JetLimeEventStyle.kt
│ │ ├── JetLimeExtendedEvent.kt
│ │ ├── JetLimeList.kt
│ │ └── JetLimeStyle.kt
│ └── test/
│ └── java/
│ └── com/
│ └── pushpal/
│ └── jetlime/
│ └── ExampleUnitTest.kt
├── public-key.asc
├── sample/
│ ├── composeApp/
│ │ ├── build.gradle.kts
│ │ ├── composeApp.podspec
│ │ └── src/
│ │ ├── androidMain/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── pushpal/
│ │ │ │ └── jetlime/
│ │ │ │ └── sample/
│ │ │ │ ├── JetLimePreviews.kt
│ │ │ │ └── MainActivity.kt
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable-v24/
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── mipmap-anydpi-v26/
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values/
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ └── values-night/
│ │ │ └── themes.xml
│ │ ├── commonMain/
│ │ │ ├── composeResources/
│ │ │ │ └── drawable/
│ │ │ │ ├── icon_change.xml
│ │ │ │ └── icon_check.xml
│ │ │ └── kotlin/
│ │ │ ├── App.kt
│ │ │ ├── Home.kt
│ │ │ ├── data/
│ │ │ │ └── Item.kt
│ │ │ ├── theme/
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Shape.kt
│ │ │ │ └── Theme.kt
│ │ │ └── timelines/
│ │ │ ├── BasicDashedTimeLine.kt
│ │ │ ├── BasicHorizontalTimeLine.kt
│ │ │ ├── BasicVerticalTimeLine.kt
│ │ │ ├── CustomizedHorizontalTimeLine.kt
│ │ │ ├── CustomizedVerticalTimeLine.kt
│ │ │ ├── ExtendedVerticalTimeLine.kt
│ │ │ ├── VerticalDynamicTimeLine.kt
│ │ │ └── event/
│ │ │ └── EventContent.kt
│ │ ├── desktopMain/
│ │ │ └── kotlin/
│ │ │ └── Main.kt
│ │ ├── iosMain/
│ │ │ └── kotlin/
│ │ │ └── MainViewController.kt
│ │ ├── jsMain/
│ │ │ ├── kotlin/
│ │ │ │ └── Main.kt
│ │ │ └── resources/
│ │ │ ├── index.html
│ │ │ └── styles.css
│ │ └── wasmJsMain/
│ │ ├── kotlin/
│ │ │ └── Main.kt
│ │ └── resources/
│ │ ├── index.html
│ │ └── styles.css
│ └── iosApp/
│ ├── Configuration/
│ │ └── Config.xcconfig
│ ├── Podfile
│ ├── Pods/
│ │ ├── Pods.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ └── xcuserdata/
│ │ │ └── pushpalroy.xcuserdatad/
│ │ │ └── xcschemes/
│ │ │ ├── Pods-iosApp.xcscheme
│ │ │ └── xcschememanagement.plist
│ │ └── Target Support Files/
│ │ └── Pods-iosApp/
│ │ ├── Pods-iosApp-Info.plist
│ │ ├── Pods-iosApp-acknowledgements.markdown
│ │ ├── Pods-iosApp-acknowledgements.plist
│ │ ├── Pods-iosApp-dummy.m
│ │ ├── Pods-iosApp-umbrella.h
│ │ ├── Pods-iosApp.debug.xcconfig
│ │ ├── Pods-iosApp.modulemap
│ │ └── Pods-iosApp.release.xcconfig
│ ├── iosApp/
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── ContentView.swift
│ │ ├── Info.plist
│ │ ├── Preview Content/
│ │ │ └── Preview Assets.xcassets/
│ │ │ └── Contents.json
│ │ └── iOSApp.swift
│ ├── iosApp.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── xcuserdata/
│ │ └── pushpalroy.xcuserdatad/
│ │ └── xcschemes/
│ │ ├── iosApp.xcscheme
│ │ └── xcschememanagement.plist
│ └── iosApp.xcworkspace/
│ ├── contents.xcworkspacedata
│ ├── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata/
│ └── pushpalroy.xcuserdatad/
│ ├── UserInterfaceState.xcuserstate
│ └── xcschemes/
│ └── xcschememanagement.plist
├── scripts/
│ ├── add_git_tag.sh
│ ├── build_android.sh
│ ├── build_ios.sh
│ ├── build_macos.sh
│ ├── build_web_js.sh
│ ├── build_web_wasm.sh
│ ├── run_dokka.sh
│ └── run_spotless.sh
├── settings.gradle.kts
└── spotless/
└── copyright.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .claude/commands/bump-version.md
================================================
---
description: Upgrade the library version across all files that contain it
argument-hint: "<new-version>"
---
Bump the JetLime library version from its current value to `$ARGUMENTS` (e.g. `4.2.0`) across every file that embeds it.
## Files to update
Update **all** occurrences of the old version string with the new version `$ARGUMENTS` in:
1. `jetlime/build.gradle.kts`
- `mavenPublishing.coordinates(...)` third argument
- `cocoapods { version = "..." }`
2. `jetlime/jetlime.podspec`
- `spec.version = '...'`
3. `scripts/add_git_tag.sh`
- `TAG="..."`
4. `README.md`
- The `implementation("io.github.pushpalroy:jetlime:...")` snippet
## Steps
1. Read each file listed above and locate the current version string.
2. Replace every occurrence of the current version with `$ARGUMENTS` using the Edit tool.
3. After all edits, grep the repo for the old version to confirm no stray occurrences remain:
```
grep -r "<old-version>" --include="*.kts" --include="*.kt" --include="*.sh" --include="*.md" --include="*.podspec" .
```
If any hits remain outside `build/`, `docs/`, or `.git/`, report them and ask whether to update them too.
4. Print a summary table — one row per file — showing the old value replaced and the new value written.
## Constraints
- Do NOT commit or tag. The user will review the diff and commit manually (or run `/dokka` and publish first).
- Do NOT modify `CHANGELOG`, `docs/`, or any generated output — those are updated separately.
- If `$ARGUMENTS` is missing or doesn't look like a semver string (`X.Y.Z`), stop and ask the user to provide it.
================================================
FILE: .claude/commands/ci-local.md
================================================
---
description: Run the full CI pipeline locally (spotless + all platform builds) to catch failures before pushing
argument-hint: "[fast|full]"
---
Mirror the GitHub Actions `build.yml` pipeline locally so the user can verify everything CI runs before pushing. CI matrix: `spotlessCheck`, `build_android.sh`, `build_web_js.sh`, `build_web_wasm.sh`, `build_macos.sh`, `build_ios.sh`.
## Behavior
Run these in sequence with fail-fast semantics. As soon as one step fails, stop and show the failing step's last ~30 lines of output — do NOT continue to later steps, because cascaded failures hide the real error.
### `$1 == "fast"` (or when user wants a quick pre-push check)
Run only the cheap/fast steps:
1. `./gradlew spotlessCheck`
2. `./scripts/build_android.sh`
3. `./scripts/build_web_js.sh`
Skip wasm, desktop, and iOS.
### `$1 == "full"` or no argument (default — matches CI exactly)
Run every CI step, in order:
1. `./gradlew spotlessCheck`
2. `./scripts/build_android.sh`
3. `./scripts/build_web_js.sh`
4. `./scripts/build_web_wasm.sh`
5. `./scripts/build_macos.sh`
6. `./scripts/build_ios.sh` — only on macOS; skip with a note on other OSes.
## Handling the common failures
- **`spotlessCheck` fails**: tell the user to run `/spotless apply` (or `./gradlew spotlessApply`), review the diff, and re-run. Do NOT auto-apply — they should see what changed.
- **`kotlinStoreYarnLock` fails during a web build** ("Lock file was changed..."): run `./gradlew kotlinUpgradeYarnLock`, then tell the user to commit `kotlin-js-store/yarn.lock` (and `kotlin-js-store/wasm/yarn.lock` if it changed). Do NOT commit automatically.
- **Gradle daemon / class-cast weirdness after a Kotlin or AGP bump**: suggest `./gradlew --stop` and a re-run.
- **iOS build on non-macOS host**: skip with a clear message — CI runs this only on `macos-latest`.
## Final report
After all steps succeed, print a single-line summary naming each step that ran and passed. No celebratory wall of text.
If a step failed, the summary is: which step, which file/task, and the recommended next command — nothing more.
================================================
FILE: .claude/commands/dokka.md
================================================
---
description: Generate API documentation using Dokka V2 and sync to docs folder
---
Generate HTML API documentation for the JetLime library. This command runs the Dokka V2 generation task and synchronizes the output to the root `docs/` directory, which is served via GitHub Pages.
Behavior:
1. Run `./scripts/run_dokka.sh`. This script executes the `:jetlime:syncDokkaToDocs` Gradle task.
2. The task generates HTML documentation using Dokka V2 from the source files.
3. It incorporates additional documentation from `dokkaModule.md` and `dokkaPackage.md` if present.
4. It clears the existing `docs/` directory and copies the new documentation there.
Notes:
- The output in `docs/` should be reviewed if significant API changes were made.
- Dokka V2 is used, as configured in `jetlime/build.gradle.kts`.
- The synchronization task ensures that the latest documentation is ready for deployment to GitHub Pages.
- If the command fails, check the Gradle output for configuration or source errors.
================================================
FILE: .claude/commands/spotless.md
================================================
---
description: Run Spotless formatting check and auto-fix any violations
argument-hint: "[check|apply]"
---
Run Spotless on this project. CI runs `spotlessCheck`, so formatting violations block merges — the goal of this command is to keep the branch clean.
Behavior based on `$1`:
- **`check`** (or no argument): run `./gradlew spotlessCheck`. If it passes, report "Spotless passes" and stop. If it fails, show the violating files from the output — do NOT auto-apply.
- **`apply`**: run `./gradlew spotlessApply`, then `./gradlew spotlessCheck` to confirm. If files were modified, list them with `git status --short` so the user can review and commit.
Notes:
- Spotless uses ktlint + `io.nlopez.compose.rules:ktlint` and a mandatory MIT license header from `spotless/copyright.kt`. Do not hand-edit formatting — let `spotlessApply` own it.
- After `apply`, do not commit or push automatically. Show the diff summary and let the user decide.
- If the Gradle build itself errors (not just formatting), surface the real error rather than retrying.
================================================
FILE: .claude/skills/compose-expert/SKILL.md
================================================
---
name: compose-expert
description: >
Compose and Compose Multiplatform expert skill for UI development across Android, Desktop,
iOS, and Web. Guides state management, view composition, animations, navigation, performance,
design-to-code workflows, and production crash patterns. Backed by actual source code analysis
from both androidx/androidx and JetBrains/compose-multiplatform-core.
Use this skill whenever the user mentions Compose, @Composable, remember, LaunchedEffect,
Scaffold, NavHost, MaterialTheme, LazyColumn, Modifier, recomposition, Style, styleable,
MutableStyleState, Compose Multiplatform, CMP, KMP, commonMain, expect, actual,
ComposeUIViewController, Window composable, UIKitView, ComposeViewport, Res.drawable,
Res.string, or any Compose API. Also trigger when the user says "Android UI", "Kotlin UI",
"compose layout", "compose navigation", "compose animation", "material3", "compose styles",
"compose multiplatform", "desktop compose", "iOS compose", "compose web", "design to compose",
"build this UI", "implement this design", "Android TV", "Google TV", "tv-material",
"tv-foundation", "Carousel", "NavigationDrawer", "D-pad", "focus indication",
"10-foot UI", "living room", "tv compose", "review this PR", "review this code",
"check this diff", or any GitHub PR URL (github.com/.*/pull/),
"design system", "component library", "atomic", "reusable component",
"design tokens", "atoms", "molecules", or asks about modern
Kotlin UI development patterns. Even casual mentions like "my compose screen is slow"
or "how do I pass data between screens" or "how do I build a TV app" should trigger this skill.
Also trigger on session_start to auto-detect Compose projects — see references/auto-init.md.
version: 2.1.2
---
> **Installation notice:** This skill is now distributed as a plugin.
> If you copied files into `~/.claude/skills/` manually, you are on an
> unmaintained install path and will not receive updates. Migrate via:
>
> /plugin marketplace add aldefy/compose-skill
> /plugin install compose-expert
>
> See [MIGRATION.md](../docs/MIGRATION.md) for Codex and Copilot CLI instructions.
> This banner will remain through v2.x and escalate in v3.0.
# Compose Expert Skill
Non-opinionated, practical guidance for writing correct, performant Compose code —
across Android, Desktop, iOS, and Web. Covers Jetpack Compose and Compose Multiplatform.
Backed by analysis of actual source code from `androidx/androidx` and
`JetBrains/compose-multiplatform-core`.
## Review Mode
**Activate when** the input contains a GitHub PR URL (`github.com/.+/pull/\d+`) or
explicit review phrases: "review this PR", "review this diff", "check this code",
"what's wrong with this".
When Review Mode activates:
1. Do **not** follow the generation workflow below
2. Read `references/pr-review.md` and follow its workflow exclusively
3. Output a structured local review report — do not post to GitHub
## Workflow
When helping with Compose code, follow this checklist:
### 1. Understand the request
- What Compose layer is involved? (Runtime, UI, Foundation, Material3, Navigation)
- Is this a state problem, layout problem, performance problem, or architecture question?
- Is this Android-only or Compose Multiplatform (CMP)?
### 2. Analyze the design (if visual reference provided)
- If the user shares a Figma frame, screenshot, or design spec, consult `references/design-to-compose.md`
- Decompose the design into a composable tree using the 5-step methodology
- Map design tokens to MaterialTheme, spacing to CompositionLocals
- Identify animation needs and consult `references/animation.md` for recipes
### 3. Consult the right reference
Read the relevant reference file(s) from `references/` before answering:
| Topic | Reference File |
|-------|---------------|
| `@State`, `remember`, `mutableStateOf`, state hoisting, `derivedStateOf`, `snapshotFlow` | `references/state-management.md` |
| Structuring composables, slots, extraction, preview | `references/view-composition.md` — for design system structure, also see `references/atomic-design.md` |
| Modifier ordering, custom modifiers, `Modifier.Node` | `references/modifiers.md` |
| `LaunchedEffect`, `DisposableEffect`, `SideEffect`, `rememberCoroutineScope` | `references/side-effects.md` |
| `CompositionLocal`, `LocalContext`, `LocalDensity`, custom locals | `references/composition-locals.md` |
| `LazyColumn`, `LazyRow`, `LazyGrid`, `Pager`, keys, content types | `references/lists-scrolling.md` |
| `NavHost`, type-safe routes, deep links, shared element transitions | `references/navigation.md` |
| `animate*AsState`, `AnimatedVisibility`, `Crossfade`, transitions | `references/animation.md` — for M3 token selection, also see `references/material3-motion.md` |
| `MaterialTheme`, `ColorScheme`, dynamic color, `Typography`, shapes | `references/theming-material3.md` — for motion, see `references/material3-motion.md`; for design tokens, see `references/atomic-design.md` |
| Recomposition skipping, stability, baseline profiles, benchmarking | `references/performance.md` |
| Semantics, content descriptions, traversal order, testing | `references/accessibility.md` |
| Removed/replaced APIs, migration paths from older Compose versions | `references/deprecated-patterns.md` |
| **Styles API** (experimental): `Style {}`, `MutableStyleState`, `Modifier.styleable()` | `references/styles-experimental.md` |
| Figma/screenshot decomposition, design tokens, spacing, modifier ordering | `references/design-to-compose.md` |
| Production crash patterns, defensive coding, state/performance rules | `references/production-crash-playbook.md` |
| Compose Multiplatform, `expect`/`actual`, resources (`Res.*`), migration | `references/multiplatform.md` |
| Desktop (Window, Tray, MenuBar), iOS (UIKitView), Web (ComposeViewport) | `references/platform-specifics.md` |
| TV Compose: Surface, Carousel, NavigationDrawer, Cards, focus, D-pad | `references/tv-compose.md` |
| M3 motion tokens, `MotionTokens`, `MotionScheme`, animation duration, easing | `references/material3-motion.md` |
| PR URL, code review, "review this PR", "what's wrong with this" | `references/pr-review.md` |
| Session start, project detection | `references/auto-init.md` |
| Atomic design, design system, reusable component, component library, design tokens | `references/atomic-design.md` |
### 4. Apply and verify
- Write code that follows the patterns in the reference
- Flag any anti-patterns you see in the user's existing code
- Suggest the minimal correct solution — don't over-engineer
### 4a. Component building mode
When the request involves building a component (composable that renders UI):
- Consult `references/atomic-design.md`
- Classify the component level (atom, molecule, organism, template)
- Apply the "Ask" prompt from Section 5 before scaffolding code
- Ensure the component satisfies the atom contract (modifier, slots, tokens, defaults)
### 5. Cite the source
When referencing Compose internals, point to the exact source file:
```
// See: compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
```
## Key Principles
1. **Compose thinks in three phases**: Composition → Layout → Drawing. State reads in each
phase only trigger work for that phase and later ones.
2. **Recomposition is frequent and cheap** — but only if you help the compiler skip unchanged
scopes. Use stable types, avoid allocations in composable bodies.
3. **Modifier order matters**. `Modifier.padding(16.dp).background(Color.Red)` is visually
different from `Modifier.background(Color.Red).padding(16.dp)`.
4. **State should live as low as possible** and be hoisted only as high as needed. Don't put
everything in a ViewModel just because you can.
5. **Side effects exist to bridge Compose's declarative world with imperative APIs**. Use the
right one for the job — misusing them causes bugs that are hard to trace.
6. **Compose Multiplatform shares the runtime but not the platform**. UI code in
`commonMain` is portable. Platform-specific APIs (`LocalContext`, `BackHandler`,
`Window`) require `expect`/`actual` or conditional source sets.
## Source Code Receipts
Beyond the guidance docs, this skill bundles the **actual source code** from
`androidx/androidx` (branch: `androidx-main`) and `JetBrains/compose-multiplatform-core`
(branch: `jb-main`). When you need to verify how something works internally, or the
user asks "show me the actual implementation", read the raw source from
`references/source-code/`:
| Module | Source Reference | Key Files Inside |
|--------|-----------------|------------------|
| Runtime | `references/source-code/runtime-source.md` | Composer.kt, Recomposer.kt, State.kt, Effects.kt, CompositionLocal.kt, Remember.kt, SlotTable.kt, Snapshot.kt |
| UI | `references/source-code/ui-source.md` | AndroidCompositionLocals.android.kt, Modifier.kt, Layout.kt, LayoutNode.kt, ModifierNodeElement.kt, DrawModifier.kt |
| Foundation | `references/source-code/foundation-source.md` | LazyList.kt, LazyGrid.kt, BasicTextField.kt, Clickable.kt, Scrollable.kt, Pager.kt |
| Material3 | `references/source-code/material3-source.md` | MaterialTheme.kt, ColorScheme.kt, Button.kt, Scaffold.kt, TextField.kt, NavigationBar.kt |
| Navigation | `references/source-code/navigation-source.md` | NavHost.kt, ComposeNavigator.kt, NavGraphBuilder.kt, DialogNavigator.kt |
| CMP | `references/source-code/cmp-source.md` | Window.kt, ComposeUIViewController.kt, UIKitView.kt, ComposeViewport.kt, ResourceReader.kt |
### Two-layer approach
1. **Start with guidance** — read the topic-specific reference (e.g., `references/state-management.md`)
2. **Go deeper with source** — if the user wants receipts or you need to verify, read from `references/source-code/`
### Source tree map
```
androidx/androidx (branch: androidx-main)
├── compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/
├── compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/
├── compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/
├── compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/
├── compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/
├── compose/navigation/navigation-compose/src/commonMain/kotlin/androidx/navigation/compose/
├── tv/tv-material/src/main/java/androidx/tv/material3/
└── tv/tv-foundation/src/main/java/androidx/tv/foundation/
compose-multiplatform-core (branch: jb-main)
├── compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/window/
├── compose/ui/ui/src/iosMain/kotlin/androidx/compose/ui/window/
├── compose/ui/ui/src/webMain/kotlin/androidx/compose/ui/window/
├── compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/
└── compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/
compose-multiplatform (resources library)
└── components/resources/library/src/commonMain/
```
================================================
FILE: .claude/skills/compose-expert/references/accessibility.md
================================================
# Accessibility Reference
## Semantics — Exposing UI to Accessibility Services
Semantics describe UI elements to screen readers, voice commands, and testing tools. Compose generates semantics automatically for built-in components, but custom views require manual annotation.
```kotlin
// Built-in components have semantics
Button(onClick = { }) { Text("Click me") } // Auto-announces as button
// Custom composables need explicit semantics
Box(
modifier = Modifier.semantics {
role = Role.Button
onClick(label = "Activate") { true }
}
) {
Text("Custom button")
}
```
**Source**: `androidx/compose/ui/semantics/`
---
## contentDescription — Labels for Accessibility
Every meaningful visual element needs a label for screen readers.
### When to Set contentDescription
**DO**: Images, icons, buttons without text, decorative with meaningful purpose
```kotlin
// Icon in a button: set contentDescription
Button(onClick = { }) {
Icon(Icons.Default.Settings, contentDescription = "App settings")
}
// Text-only button: contentDescription auto-generated
Button(onClick = { }) { Text("Save") }
// Standalone image: always set description
Image(
painter = painterResource(R.drawable.product),
contentDescription = "Product photo: wireless headphones"
)
```
### When to Set contentDescription = null
**Purely decorative images** that convey no information:
```kotlin
// Decorative divider line
Divider(modifier = Modifier.padding(vertical = 8.dp))
// Purely decorative background
Box(
modifier = Modifier
.background(Color.Gray)
.size(100.dp)
) {
Image(
painter = painterResource(R.drawable.background_pattern),
contentDescription = null // Purely decorative
)
}
// Icon next to label: skip icon description
Row {
Icon(
painter = painterResource(R.drawable.verified),
contentDescription = null, // Label below describes it
tint = Color.Green
)
Text("Verified")
}
```
Omitting `contentDescription` or using `null` tells the screen reader to skip the element.
---
## Modifier.semantics — Merging and Overriding
### Default Merging
By default, child semantics merge with parents:
```kotlin
// Child text is included in parent semantics
Column(modifier = Modifier.semantics { heading() }) {
Text("Section Title") // Screen reader: "Section Title, heading"
}
```
### Clearing and Setting Semantics
Use `clearAndSetSemantics` to override all child semantics:
```kotlin
// Screen reader ignores children, announces custom label
Box(
modifier = Modifier.clearAndSetSemantics {
contentDescription = "Custom audio player with play/pause"
}
) {
Icon(Icons.Default.PlayArrow, contentDescription = null)
Text("00:30") // Not read
Icon(Icons.Default.VolumeUp, contentDescription = null) // Not read
}
```
### Merging Disabled
Prevent children from merging:
```kotlin
Box(
modifier = Modifier.semantics(mergeDescendants = false) {
heading()
}
) {
Text("Heading") // Announced separately, not merged
}
```
---
## Touch Target Sizing
Minimum touch target is 48dp × 48dp per Material Design and WCAG guidelines.
```kotlin
// Small button without sufficient touch target
Button(modifier = Modifier.size(32.dp)) { } // TOO SMALL
// Proper touch target
Button(
modifier = Modifier.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
) { }
// Alternative: add padding
Box(
modifier = Modifier
.size(32.dp)
.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
) {
Icon(Icons.Default.Edit, contentDescription = "Edit")
}
// For clickable elements without Button
Box(
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.clickable { /* ... */ }
.sizeIn(minWidth = 48.dp, minHeight = 48.dp),
contentAlignment = Alignment.Center
) {
Icon(Icons.Default.Close, contentDescription = "Close")
}
```
---
## Headings — Screen Reader Navigation
Headings allow users to navigate by section. Set heading semantic:
```kotlin
// Level 1 heading
Text(
text = "Products",
modifier = Modifier.semantics { heading() },
style = MaterialTheme.typography.headlineLarge
)
// Subheading (no formal levels in Compose; use structure)
Text(
text = "New Arrivals",
modifier = Modifier.semantics { heading() },
style = MaterialTheme.typography.headlineMedium
)
```
Screen readers announce "heading" and allow jumping between sections. Use headings to structure content logically, not for styling.
---
## Custom Actions
Allow screen readers to trigger complex interactions:
```kotlin
@Composable
fun SlideToUnlock() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(64.dp)
.semantics {
customActions = listOf(
CustomAccessibilityAction(label = "Unlock") {
unlock()
true
},
CustomAccessibilityAction(label = "Emergency call") {
emergencyCall()
true
}
)
}
.background(MaterialTheme.colorScheme.primary)
) {
Text("Slide to Unlock", color = Color.White)
}
}
```
Custom actions appear in the accessibility menu. Avoid for standard interactions (Button, Checkbox).
---
## Traversal Order
Control screen reader navigation order explicitly when needed:
```kotlin
// Default: top-to-bottom, left-to-right
Row {
Button(onClick = { }) { Text("First") }
Button(onClick = { }) { Text("Second") }
}
// Custom order (right-to-left)
Row {
Button(
onClick = { },
modifier = Modifier.semantics { traversalIndex = 1f }
) { Text("Read Second") }
Button(
onClick = { },
modifier = Modifier.semantics { traversalIndex = 0f }
) { Text("Read First") }
}
// Group items as single traversal unit
Column(
modifier = Modifier.semantics(mergeDescendants = false) {
isTraversalGroup = true
}
) {
Text("Label")
Text("Value")
}
```
Use `traversalIndex` sparingly. Good structure is usually sufficient.
---
## State Descriptions
Inform users of component state (enabled/disabled, checked/unchecked):
```kotlin
@Composable
fun AccessibleCheckbox(
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
label: String
) {
Row(
modifier = Modifier
.clip(RoundedCornerShape(8.dp))
.clickable { onCheckedChange(!checked) }
.semantics {
this.contentDescription = label
this.stateDescription = if (checked) "Checked" else "Unchecked"
role = Role.Checkbox
}
.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
) {
Icon(
if (checked) Icons.Default.CheckBox else Icons.Default.CheckBoxOutlineBlank,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
Text(label, modifier = Modifier.padding(start = 12.dp))
}
}
```
Screen reader announces: "Label, Checkbox, Checked" or "Label, Checkbox, Unchecked".
---
## Live Regions
Announce dynamic content changes without requiring interaction:
```kotlin
@Composable
fun LiveMessage(message: String, modifier: Modifier = Modifier) {
Text(
text = message,
modifier = modifier.semantics {
liveRegion = LiveRegionMode.Assertive // Polite or Assertive
}
)
}
// Usage
var status by remember { mutableStateOf("Loading...") }
LaunchedEffect(Unit) {
delay(2000)
status = "Done loading" // Screen reader announces immediately
}
LiveMessage(message = status)
```
**Assertive**: Interrupts current speech. Use for critical updates (error, alert).
**Polite**: Queues after current speech. Use for status updates.
---
## Testing Semantics
Use Compose Test APIs to verify accessibility:
```kotlin
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun testContentDescription() {
composeTestRule.setContent {
Icon(
Icons.Default.Settings,
contentDescription = "App settings"
)
}
composeTestRule
.onNodeWithContentDescription("App settings")
.assertIsDisplayed()
}
@Test
fun testHeading() {
composeTestRule.setContent {
Text("Main Title", modifier = Modifier.semantics { heading() })
}
composeTestRule
.onNode(isHeading())
.assertIsDisplayed()
.assertTextEquals("Main Title")
}
@Test
fun testTouchTarget() {
composeTestRule.setContent {
Button(modifier = Modifier.size(32.dp)) { Text("Too small") }
}
composeTestRule
.onNodeWithText("Too small")
.assertHeightIsAtLeast(48.dp) // Fails: 32.dp < 48.dp
}
@Test
fun testCustomAction() {
composeTestRule.setContent {
Box(
modifier = Modifier.semantics {
customActions = listOf(
CustomAccessibilityAction("Unlock") { true }
)
}
)
}
composeTestRule
.onNode(hasCustomAccessibilityAction("Unlock"))
.performCustomAccessibilityAction("Unlock")
}
```
---
## Anti-Patterns
### Decorative Images Without null contentDescription
```kotlin
// DON'T: Screen reader reads useless description
Image(
painter = painterResource(R.drawable.separator_line),
contentDescription = "Line" // No value
)
// DO
Image(
painter = painterResource(R.drawable.separator_line),
contentDescription = null
)
```
### Clickable Without Semantics
```kotlin
// DON'T: No semantic role or description
Box(
modifier = Modifier
.clickable { deleteItem() }
.size(32.dp)
) {
Icon(Icons.Default.Delete, contentDescription = null)
}
// DO
Box(
modifier = Modifier
.clickable { deleteItem() }
.sizeIn(minWidth = 48.dp, minHeight = 48.dp)
.semantics {
role = Role.Button
contentDescription = "Delete item"
},
contentAlignment = Alignment.Center
) {
Icon(Icons.Default.Delete, contentDescription = null)
}
```
### Hardcoded Content Descriptions Without Context
```kotlin
// DON'T: Generic, doesn't describe purpose
Icon(Icons.Default.Star, contentDescription = "Icon")
// DO: Specific
Icon(Icons.Default.Star, contentDescription = "Add to favorites")
```
### Missing Heading Structure
```kotlin
// DON'T: No navigation structure
Column {
Text("Section 1")
Text("Section 2")
Text("Section 3")
}
// DO
Column {
Text("Section 1", modifier = Modifier.semantics { heading() })
Text("Section 2", modifier = Modifier.semantics { heading() })
Text("Section 3", modifier = Modifier.semantics { heading() })
}
```
---
## Resources
- **Compose Accessibility**: https://developer.android.com/develop/ui/compose/accessibility
- **Material Design Accessibility**: https://m3.material.io/foundations/accessible-design
- **WCAG 2.1**: https://www.w3.org/WAI/WCAG21/quickref/
- **Testing A11y in Compose**: https://developer.android.com/develop/ui/compose/testing#a11y
================================================
FILE: .claude/skills/compose-expert/references/animation.md
================================================
# Animation in Jetpack Compose
Reference: `androidx/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/`
## State-Based Animations
### animate*AsState
Animate individual properties by targeting a value. The animation starts when the value changes.
```kotlin
val size by animateDpAsState(
targetValue = if (isExpanded) 200.dp else 100.dp,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "size"
)
Box(modifier = Modifier.size(size))
```
Common variants:
```kotlin
animateColorAsState(targetValue = Color.Blue)
animateFloatAsState(targetValue = 1f)
animateIntAsState(targetValue = 100)
animateOffsetAsState(targetValue = Offset(10f, 20f))
```
Each automatically handles coroutines and recomposition. Use the `label` parameter for debugging.
## AnimatedVisibility
Controls appear/disappear animations with enter and exit transitions.
```kotlin
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(visible = visible) {
Text("Hello!")
}
// Trigger
Button(onClick = { visible = !visible }) { Text("Toggle") }
```
### Enter/Exit Transitions
```kotlin
AnimatedVisibility(
visible = visible,
enter = slideInHorizontally(initialOffsetX = { -it }) + fadeIn(),
exit = slideOutHorizontally(targetOffsetX = { -it }) + fadeOut()
) {
Text("Animated!")
}
```
Built-in transitions:
- `slideInVertically`, `slideOutVertically`
- `slideInHorizontally`, `slideOutHorizontally`
- `expandVertically`, `shrinkVertically`
- `expandHorizontally`, `shrinkHorizontally`
- `fadeIn`, `fadeOut`
- `scaleIn`, `scaleOut`
- Combine with `+`: `slideInVertically() + fadeIn()`
### Advanced: Custom animation specs
```kotlin
AnimatedVisibility(
visible = visible,
enter = slideInVertically(
initialOffsetY = { fullHeight -> fullHeight },
animationSpec = spring()
),
exit = slideOutVertically(
targetOffsetY = { fullHeight -> fullHeight },
animationSpec = tween(durationMillis = 300)
)
) {
Box(Modifier.fillMaxWidth().height(100.dp).background(Color.Blue))
}
```
## AnimatedContent
Replace content with smooth transitions.
```kotlin
var count by remember { mutableStateOf(0) }
AnimatedContent(targetState = count) { target ->
Text(text = "Count: $target")
}
Button(onClick = { count++ }) { Text("Increment") }
```
### Custom transitionSpec
```kotlin
AnimatedContent(
targetState = count,
transitionSpec = {
slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it })
}
) { target ->
Text("$target")
}
```
Use `with` to specify exit and enter together. This runs exits and entries simultaneously.
### Sequencing transitions
```kotlin
AnimatedContent(
targetState = count,
transitionSpec = {
slideInVertically(initialOffsetY = { it }) with slideOutVertically(targetOffsetY = { -it }) using SizeTransform(clip = false)
}
) { target ->
Text(
"Count: $target",
modifier = Modifier.fillMaxWidth()
)
}
```
`SizeTransform` animates container size smoothly during content changes.
## Crossfade
Simple content swap with fade effect.
```kotlin
var showFirst by remember { mutableStateOf(true) }
Crossfade(targetState = showFirst) { state ->
if (state) {
Text("First")
} else {
Text("Second")
}
}
```
Lightweight alternative to `AnimatedContent` for simple visibility toggles.
## updateTransition
Coordinate multiple animated values with a single state.
```kotlin
var expanded by remember { mutableStateOf(false) }
val transition = updateTransition(targetState = expanded)
val size by transition.animateDp { if (it) 200.dp else 100.dp }
val color by transition.animateColor { if (it) Color.Blue else Color.Red }
Box(
modifier = Modifier
.size(size)
.background(color)
.clickable { expanded = !expanded }
)
```
All animations run in sync, controlled by a single state change. Useful for complex components with multiple animated properties.
## rememberInfiniteTransition
Create looping animations.
```kotlin
val infiniteTransition = rememberInfiniteTransition(label = "infinite")
val alpha by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(1000),
repeatMode = RepeatMode.Reverse
),
label = "alpha"
)
Text("Pulsing", modifier = Modifier.alpha(alpha))
```
Runs continuously until the composable is removed. Perfect for loading states, pulsing indicators.
## Animatable
Imperative animation control in coroutines. Use for fine-grained control.
```kotlin
val animatable = remember { Animatable(0f) }
LaunchedEffect(trigger) {
animatable.animateTo(
targetValue = 100f,
animationSpec = spring()
)
}
Box(Modifier.graphicsLayer(translationX = animatable.value))
```
Useful for responding to gestures or complex conditions:
```kotlin
val animatable = remember { Animatable(0f) }
LaunchedEffect(Unit) {
animatable.animateTo(targetValue = 360f, animationSpec = tween(2000))
}
Box(
Modifier
.size(100.dp)
.background(Color.Blue)
.graphicsLayer(rotationZ = animatable.value)
)
```
## Animation Specifications
### spring — Realistic, physics-based
```kotlin
val size by animateDpAsState(
targetValue = 200.dp,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow)
)
```
- `dampingRatio`: `NoBouncy` (1f), `LowBouncy` (0.75f), `MediumBouncy` (0.5f), `HighBouncy` (0.2f)
- `stiffness`: `Low`, `Medium`, `High`
Use for interactive feedback, familiar to users.
### tween — Time-based
```kotlin
val color by animateColorAsState(
targetValue = Color.Blue,
animationSpec = tween(durationMillis = 500, easing = EaseInOutCubic)
)
```
Easing functions: `EaseInQuad`, `EaseOutQuad`, `EaseInOutQuad`, `LinearEasing`, `FastOutSlowInEasing`.
Predictable timing, good for sequential animations.
### keyframes — Frame-by-frame control
```kotlin
val position by animateFloatAsState(
targetValue = 100f,
animationSpec = keyframes {
0f at 0 using EaseInQuad
50f at 150 using EaseOutQuad
100f at 300
}
)
```
Define exact values at specific timestamps. Use for complex choreography.
## Automatic Size Animation
### animateContentSize
Smoothly animate Box size when content changes.
```kotlin
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.animateContentSize()
.background(Color.Blue)
.clickable { expanded = !expanded }
) {
Column {
Text("Header")
if (expanded) {
Text("Expanded content...")
}
}
}
```
No need for explicit `AnimatedVisibility` or layout transitions. Handles the container automatically.
## Layout Animation in LazyLists
### animateItem — Replaces animateItemPlacement
Animate item appearance, removal, and reordering.
```kotlin
LazyColumn {
items(items, key = { it.id }) { item ->
Box(
modifier = Modifier
.fillMaxWidth()
.animateItem()
.padding(8.dp)
.background(Color.Gray)
) {
Text(item.name)
}
}
}
```
Automatically animates:
- New items sliding in
- Removed items sliding out
- Reordered items moving to new positions
Called on items in Lazy layouts (LazyColumn, LazyRow, LazyVerticalGrid).
## Shared Element Transitions
Animate elements seamlessly across screen boundaries using `SharedTransitionLayout` and Navigation Compose.
### sharedElement() vs sharedBounds()
| Aspect | `sharedElement()` | `sharedBounds()` |
|---|---|---|
| **Content** | Identical on both screens (same image, same icon) | Different content in source and target (e.g., card expands to detail) |
| **Use case** | Hero image, avatar, thumbnail | Container transform, card-to-page |
| **During transition** | Only the target composable is rendered | Both source and target are visible and crossfade |
### Complete Working Example
```kotlin
@Composable
fun App() {
SharedTransitionLayout {
NavHost(navController = navController, startDestination = "list") {
composable("list") {
ListScreen(
onItemClick = { id -> navController.navigate("detail/$id") },
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@composable
)
}
composable("detail/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id") ?: return@composable
DetailScreen(
itemId = id,
sharedTransitionScope = this@SharedTransitionLayout,
animatedVisibilityScope = this@composable
)
}
}
}
}
@Composable
fun ListScreen(
onItemClick: (String) -> Unit,
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope
) {
with(sharedTransitionScope) {
Row(
modifier = Modifier
.clickable { onItemClick(item.id) }
// sharedBounds wraps the entire card container (different content at source/target)
.sharedBounds(
sharedContentState = rememberSharedContentState(key = "card-${item.id}"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = BoundsTransform { initialBounds, targetBounds ->
keyframes {
durationMillis = 500
initialBounds at 0 using ArcMode.ArcBelow
targetBounds at 500
}
}
)
) {
Image(
painter = painterResource(item.imageRes),
contentDescription = null,
modifier = Modifier
.size(80.dp)
// sharedElement for the identical image across screens
.sharedElement(
state = rememberSharedContentState(key = "image-${item.id}"),
animatedVisibilityScope = animatedVisibilityScope
)
)
Text(
text = item.title,
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = "title-${item.id}"),
animatedVisibilityScope = animatedVisibilityScope
)
// Prevent text reflow during transition by snapping to final size
.skipToLookaheadSize()
)
}
}
}
@Composable
fun DetailScreen(
itemId: String,
sharedTransitionScope: SharedTransitionScope,
animatedVisibilityScope: AnimatedVisibilityScope
) {
with(sharedTransitionScope) {
Column(
modifier = Modifier
.sharedBounds(
sharedContentState = rememberSharedContentState(key = "card-$itemId"),
animatedVisibilityScope = animatedVisibilityScope
)
) {
Image(
painter = painterResource(item.imageRes),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.sharedElement(
state = rememberSharedContentState(key = "image-$itemId"),
animatedVisibilityScope = animatedVisibilityScope
)
)
Text(
text = item.title,
style = MaterialTheme.typography.headlineMedium,
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = "title-$itemId"),
animatedVisibilityScope = animatedVisibilityScope
)
.skipToLookaheadSize()
)
// Non-shared content fades in
Text(
text = item.description,
modifier = Modifier.animateEnterExit(
enter = fadeIn() + slideInVertically { it / 3 },
exit = fadeOut()
)
)
}
}
}
```
### BoundsTransform for Arc Motion
Control the animation path between source and target bounds:
```kotlin
val arcBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
keyframes {
durationMillis = 500
initialBounds at 0 using ArcMode.ArcBelow
targetBounds at 500
}
}
// Apply to sharedElement or sharedBounds
Modifier.sharedElement(
state = rememberSharedContentState(key = "hero"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = arcBoundsTransform
)
```
### Overlay Rendering
Keep shared elements above all other content during the transition:
```kotlin
Modifier.sharedElement(
state = rememberSharedContentState(key = "fab"),
animatedVisibilityScope = animatedVisibilityScope,
renderInSharedTransitionScopeOverlay = true // Renders above navigation transitions
)
```
### Preventing Text Reflow
Use `skipToLookaheadSize()` so text composables snap to their final size immediately, avoiding awkward line-break changes mid-transition:
```kotlin
Text(
text = item.title,
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = "title-${item.id}"),
animatedVisibilityScope = animatedVisibilityScope
)
.skipToLookaheadSize() // Text uses target size immediately, no reflow
)
```
## Performance: graphicsLayer for Transforms
Animate transforms using `graphicsLayer` instead of layout changes.
```kotlin
// ✅ Correct: Uses GPU-accelerated graphicsLayer
val offset by animateFloatAsState(targetValue = 100f)
Box(modifier = Modifier.graphicsLayer(translationX = offset))
// ❌ Avoid: Causes recomposition and relayout
val offset by animateFloatAsState(targetValue = 100f)
Box(modifier = Modifier.offset(x = offset.dp))
```
Use `graphicsLayer` for:
- Translation (`translationX`, `translationY`)
- Rotation (`rotationX`, `rotationY`, `rotationZ`)
- Scale (`scaleX`, `scaleY`)
- Alpha (opacity)
## Anti-Patterns
### Don't: Animate visibility with if
```kotlin
// ❌ Anti-pattern
@Composable
fun MyScreen() {
if (visible) {
Text("Content") // Jumps in/out without animation
}
}
// ✅ Correct
@Composable
fun MyScreen() {
AnimatedVisibility(visible = visible) {
Text("Content")
}
}
```
### Don't: Create Animatable in composition
```kotlin
// ❌ Anti-pattern
@Composable
fun MyScreen() {
val animatable = Animatable(0f) // Recreated every recomposition!
LaunchedEffect(Unit) {
animatable.animateTo(100f)
}
}
// ✅ Correct
@Composable
fun MyScreen() {
val animatable = remember { Animatable(0f) } // Preserved across recompositions
LaunchedEffect(Unit) {
animatable.animateTo(100f)
}
}
```
### Don't: Animate in composition phase
```kotlin
// ❌ Anti-pattern
@Composable
fun MyScreen() {
var position by remember { mutableStateOf(0f) }
position = position + 10f // Infinite recomposition loop!
}
// ✅ Correct
@Composable
fun MyScreen() {
var position by remember { mutableStateOf(0f) }
LaunchedEffect(Unit) {
repeat(10) {
position += 10f
delay(16)
}
}
}
```
### Don't: Forget label parameter
```kotlin
// ❌ Anti-pattern (harder to debug)
val size by animateDpAsState(targetValue = 100.dp)
// ✅ Correct
val size by animateDpAsState(
targetValue = 100.dp,
label = "box_size"
)
```
Labels help with debugging layout inspector and animation inspection tools.
---
## Animation Decision Tree
### When to Use Which API
| API | Use When |
|---|---|
| `animate*AsState` | Animating a single property (size, color, alpha) driven by state |
| `AnimatedVisibility` | Showing or hiding a composable with enter/exit transitions |
| `AnimatedContent` / `Crossfade` | Switching between different composables (content swap) |
| `updateTransition` | Multiple properties that must animate in sync from the same state |
| `Animatable` | Gesture-driven or imperative control (coroutine-based, supports `snapTo`, `animateDecay`) |
| `rememberInfiniteTransition` | Infinite looping animations (pulsing, rotating, shimmer) |
| `animateContentSize` | Smoothly animating a container's size when its content changes |
| `animateItem` | List item appearance, disappearance, and reordering in Lazy layouts |
### Which Phase Each Animation Affects
Compose rendering has three phases: **Composition** (what to show), **Layout** (where to place), **Draw** (how to render). Animations should read state in the latest possible phase to minimize work.
```kotlin
// BEST: Draw phase only — no relayout, no recomposition
val alpha by animateFloatAsState(targetValue = if (visible) 1f else 0f, label = "alpha")
Box(
modifier = Modifier.graphicsLayer { this.alpha = alpha }
)
// GOOD: Layout phase only — relayout but no recomposition
val offsetPx by animateIntAsState(targetValue = if (moved) 300 else 0, label = "offset")
Box(
modifier = Modifier.offset { IntOffset(offsetPx, 0) }
)
// MODERATE: Composition + Layout — triggers recomposition on every frame
val offsetDp by animateDpAsState(targetValue = if (moved) 100.dp else 0.dp, label = "offset")
Box(
modifier = Modifier.offset(x = offsetDp)
)
```
**Rule:** Defer state reads to the latest possible phase. Use lambda-based modifiers (`graphicsLayer { }`, `offset { }`) instead of parameter-based modifiers (`graphicsLayer(alpha = ...)`, `offset(x = ...)`).
---
## Design-to-Animation Translation
### Figma Easing Curves to Compose
| Figma Easing | Compose Equivalent |
|---|---|
| Linear | `LinearEasing` |
| Ease In | `FastOutLinearInEasing` |
| Ease Out | `LinearOutSlowInEasing` |
| Ease In and Out | `FastOutSlowInEasing` |
| Custom Bezier (x1, y1, x2, y2) | `CubicBezierEasing(x1, y1, x2, y2)` |
### M3 Motion Duration Tokens
| Token | Duration |
|---|---|
| Short1 | 50ms |
| Short2 | 100ms |
| Short3 | 150ms |
| Short4 | 200ms |
| Medium1 | 250ms |
| Medium2 | 300ms |
| Medium3 | 350ms |
| Medium4 | 400ms |
| Long1 | 450ms |
| Long2 | 500ms |
| Long3 | 550ms |
| Long4 | 600ms |
| ExtraLong1 | 700ms |
| ExtraLong2 | 800ms |
| ExtraLong3 | 900ms |
| ExtraLong4 | 1000ms |
### M3 Easing Tokens
| Token | Compose Value |
|---|---|
| Emphasized | `CubicBezierEasing(0.2f, 0f, 0f, 1f)` |
| EmphasizedDecelerate | `CubicBezierEasing(0.05f, 0.7f, 0.1f, 1f)` |
| EmphasizedAccelerate | `CubicBezierEasing(0.3f, 0f, 0.8f, 0.15f)` |
| Standard | `FastOutSlowInEasing` |
| StandardDecelerate | `LinearOutSlowInEasing` |
| StandardAccelerate | `FastOutLinearInEasing` |
### Spring Parameter Intuition
**Stiffness** (how fast the animation moves toward its target):
| Value | Constant | Feel |
|---|---|---|
| ~26f | — | Slow, heavy, lethargic |
| 200f | `Spring.StiffnessLow` | Gentle, relaxed |
| 400f | `Spring.StiffnessMediumLow` | Casual, comfortable |
| 1500f | `Spring.StiffnessMedium` | Responsive, default |
| 10000f | `Spring.StiffnessHigh` | Snappy, immediate |
**Damping Ratio** (how much bounce):
| Value | Constant | Feel |
|---|---|---|
| 1.0f | `Spring.DampingRatioNoBouncy` | No overshoot, settles directly |
| 0.75f | `Spring.DampingRatioLowBouncy` | Subtle bounce, professional |
| 0.5f | `Spring.DampingRatioMediumBouncy` | Playful, noticeable bounce |
| 0.2f | `Spring.DampingRatioHighBouncy` | Exaggerated, cartoonish bounce |
### Figma Spring to Compose Conversion
```kotlin
fun figmaSpringToCompose(mass: Float, stiffness: Float, damping: Float): SpringSpec<Float> {
val dampingRatio = damping / (2f * sqrt(stiffness * mass))
return spring(dampingRatio = dampingRatio, stiffness = stiffness)
}
```
### Production-Validated Spring Specs
```kotlin
val figmaMatchedSpring = spring<Float>(dampingRatio = 0.444f, stiffness = 26.5f)
val responsiveSpring = spring<Float>(dampingRatio = 0.7f, stiffness = 800f)
val snappySpring = spring<Float>(dampingRatio = 0.6f, stiffness = 1000f)
```
---
## Gesture-Driven Animations
### Swipe-to-Dismiss with Animatable
```kotlin
fun Modifier.swipeToDismiss(onDismiss: () -> Unit): Modifier = composed {
val offsetX = remember { Animatable(0f) }
val decay = rememberSplineBasedDecay<Float>()
pointerInput(Unit) {
coroutineScope {
while (true) {
val velocityTracker = VelocityTracker()
// Wait for touch down
val pointerId = awaitPointerEventScope {
awaitFirstDown().id
}
// Cancel any ongoing animation
offsetX.stop()
awaitPointerEventScope {
horizontalDrag(pointerId) { change ->
val horizontalDragOffset = offsetX.value + change.positionChange().x
launch { offsetX.snapTo(horizontalDragOffset) }
velocityTracker.addPosition(change.uptimeMillis, change.position)
change.consume()
}
}
val velocity = velocityTracker.calculateVelocity().x
val targetOffsetX = decay.calculateTargetValue(offsetX.value, velocity)
offsetX.updateBounds(
lowerBound = -size.width.toFloat(),
upperBound = size.width.toFloat()
)
launch {
if (abs(targetOffsetX) >= size.width * 0.5f) {
// Fling far enough — dismiss
offsetX.animateDecay(velocity, decay)
onDismiss()
} else {
// Snap back
offsetX.animateTo(
targetValue = 0f,
initialVelocity = velocity
)
}
}
}
}
}.offset { IntOffset(offsetX.value.roundToInt(), 0) }
}
```
### AnchoredDraggable Snap Points
```kotlin
enum class DragValue { Start, Center, End }
@Composable
fun AnchoredDraggableExample() {
val density = LocalDensity.current
val anchors = with(density) {
DraggableAnchors {
DragValue.Start at -200.dp.toPx()
DragValue.Center at 0f
DragValue.End at 200.dp.toPx()
}
}
val state = remember {
AnchoredDraggableState(
initialValue = DragValue.Center,
anchors = anchors,
positionalThreshold = { totalDistance -> totalDistance * 0.5f },
velocityThreshold = { with(density) { 125.dp.toPx() } },
animationSpec = spring()
)
}
Box(
modifier = Modifier
.offset { IntOffset(state.requireOffset().roundToInt(), 0) }
.anchoredDraggable(state, Orientation.Horizontal)
.size(80.dp)
.background(Color.Blue, RoundedCornerShape(16.dp))
)
}
```
### Transformable: Pinch, Zoom, Rotate
```kotlin
@Composable
fun TransformableExample() {
var scale by remember { mutableFloatStateOf(1f) }
var rotation by remember { mutableFloatStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val transformableState = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
scale = (scale * zoomChange).coerceIn(0.5f, 5f)
rotation += rotationChange
offset += offsetChange
}
Box(
modifier = Modifier
.graphicsLayer {
scaleX = scale
scaleY = scale
rotationZ = rotation
translationX = offset.x
translationY = offset.y
}
.transformable(state = transformableState)
.size(200.dp)
.background(Color.Blue)
)
}
```
---
## Animation Recipes
### Shimmer / Skeleton Loading
```kotlin
fun Modifier.shimmerEffect(): Modifier = composed {
val transition = rememberInfiniteTransition(label = "shimmer")
val translateAnim by transition.animateFloat(
initialValue = -1000f,
targetValue = 1000f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 1200, easing = LinearEasing),
repeatMode = RepeatMode.Restart
),
label = "shimmer_translate"
)
val shimmerBrush = Brush.linearGradient(
colors = listOf(
Color.LightGray.copy(alpha = 0.6f),
Color.LightGray.copy(alpha = 0.2f),
Color.LightGray.copy(alpha = 0.6f)
),
start = Offset(translateAnim, 0f),
end = Offset(translateAnim + 500f, 0f)
)
background(shimmerBrush)
}
@Composable
fun SkeletonCard() {
Column(modifier = Modifier.padding(16.dp)) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(12.dp))
.shimmerEffect()
)
Spacer(modifier = Modifier.height(8.dp))
Box(
modifier = Modifier
.fillMaxWidth(0.7f)
.height(20.dp)
.clip(RoundedCornerShape(4.dp))
.shimmerEffect()
)
}
}
@Composable
fun ContentWithLoading(isLoading: Boolean, content: @Composable () -> Unit) {
Crossfade(targetState = isLoading, label = "loading_crossfade") { loading ->
if (loading) {
SkeletonCard()
} else {
content()
}
}
}
```
### Staggered List Entrance
```kotlin
@Composable
fun StaggeredListEntrance(items: List<String>) {
Column {
items.forEachIndexed { index, item ->
val animatable = remember { Animatable(0f) }
LaunchedEffect(Unit) {
delay(index * 100L)
animatable.animateTo(
targetValue = 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessMediumLow
)
)
}
Text(
text = item,
modifier = Modifier
.graphicsLayer {
alpha = animatable.value
translationX = (1f - animatable.value) * 100f
}
.padding(8.dp)
)
}
}
}
```
### Swipe-to-Dismiss (Material 3)
```kotlin
@Composable
fun SwipeToDismissItem(
onDismiss: () -> Unit,
content: @Composable () -> Unit
) {
val dismissState = rememberSwipeToDismissBoxState(
confirmValueChange = { value ->
if (value != SwipeToDismissBoxValue.Settled) {
onDismiss()
true
} else false
}
)
SwipeToDismissBox(
state = dismissState,
backgroundContent = {
val color by animateColorAsState(
targetValue = when (dismissState.targetValue) {
SwipeToDismissBoxValue.StartToEnd -> Color.Green
SwipeToDismissBoxValue.EndToStart -> Color.Red
SwipeToDismissBoxValue.Settled -> Color.Transparent
},
label = "dismiss_bg"
)
Box(
modifier = Modifier
.fillMaxSize()
.background(color)
.padding(horizontal = 20.dp),
contentAlignment = when (dismissState.targetValue) {
SwipeToDismissBoxValue.StartToEnd -> Alignment.CenterStart
else -> Alignment.CenterEnd
}
) {
Icon(
imageVector = when (dismissState.targetValue) {
SwipeToDismissBoxValue.StartToEnd -> Icons.Default.Done
else -> Icons.Default.Delete
},
contentDescription = null,
tint = Color.White
)
}
}
) {
content()
}
}
```
### Expandable Card
```kotlin
@Composable
fun ExpandableCard(title: String, description: String) {
var expanded by remember { mutableStateOf(false) }
val arrowRotation by animateFloatAsState(
targetValue = if (expanded) 180f else 0f,
label = "arrow_rotation"
)
Card(
modifier = Modifier
.fillMaxWidth()
.animateContentSize(animationSpec = spring(stiffness = Spring.StiffnessMediumLow))
.clickable { expanded = !expanded }
) {
Column(modifier = Modifier.padding(16.dp)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = title, style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f))
Icon(
imageVector = Icons.Default.KeyboardArrowDown,
contentDescription = if (expanded) "Collapse" else "Expand",
modifier = Modifier.graphicsLayer { rotationZ = arrowRotation }
)
}
AnimatedVisibility(visible = expanded) {
Text(
text = description,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(top = 8.dp)
)
}
}
}
}
```
### Pull-to-Refresh Custom
```kotlin
@Composable
fun CustomPullToRefresh(
isRefreshing: Boolean,
onRefresh: () -> Unit,
content: @Composable () -> Unit
) {
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = onRefresh,
indicator = { state ->
val distanceFraction = state.distanceFraction.coerceIn(0f, 1f)
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
contentAlignment = Alignment.TopCenter
) {
Icon(
imageVector = Icons.Default.Refresh,
contentDescription = "Refreshing",
modifier = Modifier
.size(32.dp)
.graphicsLayer {
scaleX = distanceFraction
scaleY = distanceFraction
rotationZ = distanceFraction * 360f
}
)
}
}
) {
content()
}
}
```
### FAB Morph
**Pattern 1: ExtendedFloatingActionButton with scroll-driven expand/collapse**
```kotlin
@Composable
fun CollapsibleFab(listState: LazyListState) {
val expandedFab by remember {
derivedStateOf { listState.firstVisibleItemIndex == 0 }
}
ExtendedFloatingActionButton(
onClick = { /* action */ },
expanded = expandedFab,
icon = { Icon(Icons.Default.Add, contentDescription = "Add") },
text = { Text("New Item") }
)
}
```
**Pattern 2: Exploding FAB with updateTransition**
```kotlin
@Composable
fun ExplodingFab(isExpanded: Boolean, onClick: () -> Unit) {
val transition = updateTransition(targetState = isExpanded, label = "fab_explode")
val size by transition.animateDp(label = "size") { if (it) 200.dp else 56.dp }
val cornerRadius by transition.animateDp(label = "corner") { if (it) 16.dp else 28.dp }
val color by transition.animateColor(label = "color") {
if (it) MaterialTheme.colorScheme.secondaryContainer
else MaterialTheme.colorScheme.primaryContainer
}
val contentAlpha by transition.animateFloat(label = "alpha") { if (it) 1f else 0f }
Surface(
modifier = Modifier.size(size).clickable { onClick() },
shape = RoundedCornerShape(cornerRadius),
color = color
) {
Box(contentAlignment = Alignment.Center) {
if (!isExpanded) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
Column(
modifier = Modifier.graphicsLayer { alpha = contentAlpha },
horizontalAlignment = Alignment.CenterHorizontally
) {
// Expanded content
Text("Option 1")
Text("Option 2")
Text("Option 3")
}
}
}
}
```
### Bottom Sheet Drag
```kotlin
enum class SheetValue { Hidden, Collapsed, Expanded }
@Composable
fun DraggableBottomSheet(content: @Composable () -> Unit) {
val density = LocalDensity.current
val anchors = with(density) {
DraggableAnchors {
SheetValue.Hidden at 0f
SheetValue.Collapsed at -200.dp.toPx()
SheetValue.Expanded at -600.dp.toPx()
}
}
val state = remember {
AnchoredDraggableState(
initialValue = SheetValue.Hidden,
anchors = anchors,
positionalThreshold = { totalDistance -> totalDistance * 0.5f },
velocityThreshold = { with(density) { 125.dp.toPx() } },
animationSpec = spring(stiffness = Spring.StiffnessMediumLow)
)
}
Box(modifier = Modifier.fillMaxSize()) {
content()
Surface(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.offset { IntOffset(0, (state.requireOffset()).roundToInt()) }
.anchoredDraggable(state, Orientation.Vertical),
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
shadowElevation = 8.dp
) {
Column(modifier = Modifier.fillMaxWidth().height(600.dp).padding(16.dp)) {
// Drag handle
Box(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.width(40.dp)
.height(4.dp)
.background(Color.Gray, RoundedCornerShape(2.dp))
)
Spacer(modifier = Modifier.height(16.dp))
Text("Sheet Content")
}
}
}
}
```
### Parallax Scroll Header
```kotlin
@Composable
fun ParallaxHeader(scrollState: ScrollState) {
val scrollOffset = scrollState.value.toFloat()
Box(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.graphicsLayer {
translationY = scrollOffset * 0.6f // Parallax factor
scaleX = 1f + (scrollOffset * 0.001f).coerceAtLeast(0f)
scaleY = 1f + (scrollOffset * 0.001f).coerceAtLeast(0f)
alpha = (1f - (scrollOffset / 600f)).coerceIn(0f, 1f)
}
) {
Image(
painter = painterResource(R.drawable.header),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize()
)
}
}
```
### Animated Tab Switch
```kotlin
@Composable
fun AnimatedTabContent(selectedTabIndex: Int) {
AnimatedContent(
targetState = selectedTabIndex,
transitionSpec = {
val direction = if (targetState > initialState) 1 else -1
slideInHorizontally(
initialOffsetX = { fullWidth -> direction * fullWidth },
animationSpec = tween(300)
) + fadeIn(animationSpec = tween(300)) togetherWith
slideOutHorizontally(
targetOffsetX = { fullWidth -> -direction * fullWidth },
animationSpec = tween(300)
) + fadeOut(animationSpec = tween(300)) using
SizeTransform(clip = false)
},
label = "tab_content"
) { tabIndex ->
when (tabIndex) {
0 -> TabOneContent()
1 -> TabTwoContent()
2 -> TabThreeContent()
}
}
}
```
---
## Sequential/Parallel Animation Choreography
### Sequential (Coroutine Chaining)
Each `animateTo` suspends until complete, so chaining them creates sequential animation:
```kotlin
val alpha = remember { Animatable(0f) }
val translateY = remember { Animatable(100f) }
val scale = remember { Animatable(0.5f) }
LaunchedEffect(Unit) {
alpha.animateTo(1f, animationSpec = tween(300))
translateY.animateTo(0f, animationSpec = spring())
scale.animateTo(1f, animationSpec = tween(200))
}
```
### Parallel (Multiple launch blocks)
```kotlin
val alpha = remember { Animatable(0f) }
val translateY = remember { Animatable(100f) }
LaunchedEffect(Unit) {
coroutineScope {
launch { alpha.animateTo(1f, animationSpec = tween(300)) }
launch { translateY.animateTo(0f, animationSpec = spring()) }
}
// Code here runs after BOTH animations complete
}
```
### Staggered Delays
```kotlin
val items = remember { List(5) { Animatable(0f) } }
LaunchedEffect(Unit) {
items.forEachIndexed { index, animatable ->
launch {
delay(index * 80L)
animatable.animateTo(1f, animationSpec = spring())
}
}
}
```
### Mixed Sequential + Parallel
```kotlin
LaunchedEffect(Unit) {
// Phase 1: Sequential — fade in first
alpha.animateTo(1f, animationSpec = tween(200))
// Phase 2: Parallel — move and scale at the same time
coroutineScope {
launch { translateY.animateTo(0f, animationSpec = spring()) }
launch { scale.animateTo(1f, animationSpec = spring()) }
}
// Phase 3: Sequential — final flourish after Phase 2 completes
rotation.animateTo(360f, animationSpec = tween(400))
}
```
---
## Predictive Back Gesture Animation (Android 14+)
### NavHost Transitions
```kotlin
NavHost(
navController = navController,
startDestination = "home",
enterTransition = {
slideInHorizontally(initialOffsetX = { it }) + fadeIn(animationSpec = tween(300))
},
exitTransition = {
slideOutHorizontally(targetOffsetX = { -it / 3 }) + fadeOut(animationSpec = tween(300))
},
popEnterTransition = {
slideInHorizontally(initialOffsetX = { -it / 3 }) + fadeIn(animationSpec = tween(300))
},
popExitTransition = {
slideOutHorizontally(targetOffsetX = { it }) + fadeOut(animationSpec = tween(300))
}
) {
composable("home") { HomeScreen() }
composable("detail") { DetailScreen() }
}
```
### PredictiveBackHandler
```kotlin
@Composable
fun PredictiveBackExample(onBack: () -> Unit) {
var boxScale by remember { mutableFloatStateOf(1f) }
PredictiveBackHandler(enabled = true) { progress: Flow<BackEventCompat> ->
try {
progress.collect { backEvent ->
boxScale = 1f - (0.3f * backEvent.progress)
}
onBack()
} catch (e: CancellationException) {
boxScale = 1f
throw e
}
}
Box(
modifier = Modifier
.fillMaxSize()
.graphicsLayer {
scaleX = boxScale
scaleY = boxScale
}
) {
Text("Swipe back to see scale animation")
}
}
```
### M3 Automatic Predictive Back
These Material 3 components animate with predictive back gestures out of the box (no extra code needed):
- `SearchBar` — collapses back on swipe
- `ModalBottomSheet` — slides down with gesture progress
- `ModalNavigationDrawer` — slides closed with gesture progress
---
## Additional Anti-Patterns
### Don't: Read animated state in composition when draw-phase suffices
```kotlin
// BAD: Reads alpha during composition, triggers recomposition every frame
val alpha by animateFloatAsState(targetValue = 0.5f, label = "alpha")
Box(modifier = Modifier.alpha(alpha))
// GOOD: Reads alpha during draw phase only, skips recomposition
val alpha by animateFloatAsState(targetValue = 0.5f, label = "alpha")
Box(modifier = Modifier.graphicsLayer { this.alpha = alpha })
```
### Don't: Use offset(x, y) for animated movement
```kotlin
// BAD: Parameter-based offset triggers recomposition + relayout
val animatedDp by animateDpAsState(targetValue = 100.dp, label = "x")
Box(modifier = Modifier.offset(x = animatedDp))
// BETTER: Lambda offset — layout phase only, no recomposition
val animatedPx by animateIntAsState(targetValue = 300, label = "x")
Box(modifier = Modifier.offset { IntOffset(animatedPx, 0) })
// BEST: graphicsLayer — draw phase only
val animatedPx by animateFloatAsState(targetValue = 300f, label = "x")
Box(modifier = Modifier.graphicsLayer { translationX = animatedPx })
```
### Don't: Use updateTransition for independent properties
```kotlin
// BAD: Properties don't need synchronization but are coupled
val transition = updateTransition(targetState = state, label = "t")
val alpha by transition.animateFloat(label = "a") { if (it) 1f else 0f }
val size by transition.animateDp(label = "s") { if (it) 200.dp else 100.dp }
// GOOD: Independent properties use separate animate*AsState
val alpha by animateFloatAsState(targetValue = if (state) 1f else 0f, label = "alpha")
val size by animateDpAsState(targetValue = if (state) 200.dp else 100.dp, label = "size")
```
### Don't: Hardcode arbitrary durations
```kotlin
// BAD: Arbitrary duration with no design rationale
val anim by animateFloatAsState(
targetValue = 1f,
animationSpec = tween(durationMillis = 347),
label = "anim"
)
// GOOD: Use M3 motion tokens for consistency
val anim by animateFloatAsState(
targetValue = 1f,
animationSpec = tween(durationMillis = MotionTokens.DurationMedium2.toInt()),
label = "anim"
)
// BETTER: Use spring() for interruptible, natural-feeling animations
val anim by animateFloatAsState(
targetValue = 1f,
animationSpec = spring(stiffness = Spring.StiffnessMedium),
label = "anim"
)
```
================================================
FILE: .claude/skills/compose-expert/references/atomic-design.md
================================================
# Atomic Design System Reference
Building reusable, hierarchical component systems in Jetpack Compose and Compose Multiplatform.
Based on Brad Frost's atomic design methodology, mapped to Compose primitives.
---
## 1. The 5-Level Hierarchy Mapped to Compose
| Level | Compose Equivalent | Examples |
|-------|-------------------|----------|
| **Tokens** | `MaterialTheme.colorScheme.*`, `MaterialTheme.typography.*`, custom `CompositionLocal` tokens (spacing, elevation, brand colors) | `AppTheme.spacing.medium`, `AppTheme.colors.brandPrimary` |
| **Atoms** | Single-purpose composables with one responsibility, slot API, modifier param. Either wrap M3 or build custom. | `AppButton`, `AppTextField`, `AppAvatar`, `AppIcon` |
| **Molecules** | Composables that combine 2+ atoms into a functional unit | `SearchBar` (icon + text field), `MovieCard` (image + text), `UserListItem` |
| **Organisms** | Screen sections combining molecules into a UI region | `MovieCatalogRow` (header + LazyRow of MovieCards), `NavigationDrawerWithContent` |
| **Templates** | Screen layouts defining content areas without data — `Scaffold` + slot composition | `MainScreenTemplate(topBar, content, bottomBar)`, `DetailScreenTemplate(hero, body, actions)` |
**Dependency rule:** each level depends only on levels below it. An organism should not use
raw `Text()` — it should use an atom. A molecule should not hardcode colors — it should use
tokens via `MaterialTheme` or custom `CompositionLocal`.
```
Template
└── Organism
└── Molecule
└── Atom
└── Token (MaterialTheme / CompositionLocal)
```
---
## 2. Token Layer
Tokens are the foundation. Every visual property (color, typography, spacing, shape, motion)
should come from a token — never hardcoded in a composable body.
### M3 tokens (use directly)
These are already provided by `MaterialTheme`:
- `MaterialTheme.colorScheme` — primary, secondary, surface, error, etc.
- `MaterialTheme.typography` — displayLarge through labelSmall
- `MaterialTheme.shapes` — extraSmall through extraLarge
- `MaterialTheme.motionScheme` — `defaultSpatialSpec()`, `defaultEffectsSpec()`
### App-level custom tokens
Create when M3 doesn't cover your need. Use `CompositionLocal` + a wrapper theme.
**Spacing scale:**
```kotlin
object AppSpacing {
val xxs = 2.dp
val xs = 4.dp
val sm = 8.dp
val md = 16.dp
val lg = 24.dp
val xl = 32.dp
}
val LocalAppSpacing = staticCompositionLocalOf { AppSpacing }
```
**Brand colors (beyond M3 colorScheme):**
```kotlin
data class AppBrandColors(
val accent: Color,
val onAccent: Color,
val surface: Color,
)
val LocalAppBrandColors = staticCompositionLocalOf {
AppBrandColors(
accent = Color.Unspecified,
onAccent = Color.Unspecified,
surface = Color.Unspecified,
)
}
```
**Access pattern — wrap in `AppTheme`:**
```kotlin
@Composable
fun AppTheme(content: @Composable () -> Unit) {
CompositionLocalProvider(
LocalAppSpacing provides AppSpacing,
LocalAppBrandColors provides AppBrandColors(
accent = Color(0xFF1A73E8),
onAccent = Color.White,
surface = Color(0xFFF5F5F5),
)
) {
MaterialTheme(
colorScheme = /* your color scheme */,
typography = /* your typography */,
shapes = /* your shapes */,
) {
content()
}
}
}
// Usage anywhere in the tree:
val spacing = LocalAppSpacing.current
val brandColors = LocalAppBrandColors.current
```
**When to create a custom token vs. use M3 directly:**
- M3 covers it → use `MaterialTheme.*` directly
- App-specific concept (brand accent, spacing scale, elevation scale) → custom `CompositionLocal`
- One-off value needed in a single component → not a token, just a local constant
---
## 3. Atom Patterns
Atoms are the smallest reusable UI units. Every atom must satisfy the **atom contract**.
### Atom Contract
Every atom (public composable that renders UI) must satisfy:
1. **`modifier: Modifier = Modifier` parameter** — caller controls layout
2. **Slot APIs for variable content** — `@Composable () -> Unit` or scoped like `@Composable RowScope.() -> Unit`
3. **Token-based styling** — no hardcoded `Color(0xFF...)`, `14.sp`, `FontWeight.Bold`
4. **Sensible defaults** — works without configuration
5. **Preview composable** — `@Preview` function for visual verification
### Two atom types
**1. M3 wrapper atoms** — wrap an M3 component with brand defaults:
```kotlin
@Composable
fun AppButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
enabled: Boolean = true,
content: @Composable RowScope.() -> Unit,
) {
Button(
onClick = onClick,
modifier = modifier,
enabled = enabled,
colors = ButtonDefaults.buttonColors(
containerColor = LocalAppBrandColors.current.accent,
contentColor = LocalAppBrandColors.current.onAccent,
),
content = content,
)
}
```
**2. Custom atoms** — when no M3 equivalent exists:
```kotlin
@Composable
fun AppAvatar(
imageUrl: String,
size: AvatarSize = AvatarSize.Medium,
modifier: Modifier = Modifier,
contentDescription: String? = null,
) {
AsyncImage(
model = imageUrl,
contentDescription = contentDescription,
contentScale = ContentScale.Crop,
modifier = modifier
.size(size.dp)
.clip(CircleShape)
)
}
enum class AvatarSize(val dp: Dp) {
Small(24.dp), Medium(40.dp), Large(56.dp)
}
```
### Naming rule
Name by what the component **IS**, not where it's used.
| Bad | Good | Why |
|-----|------|-----|
| `ButtonWithBoldCTA` | `AppButton` | The boldness is a style variant, not a component |
| `RedBorderCard` | `HighlightCard` or `AppCard` | Named by visual appearance, not function |
| `HomeMovieCard` | `MovieCard` | Named by screen, not reusable |
| `ButtonForSettings` | `AppButton` | Named by context, not function |
---
## 4. Molecule, Organism, and Template Patterns
### Molecule — composes 2+ atoms
A molecule combines atoms into a functional unit. It accepts data and callbacks, not ViewModels.
```kotlin
@Composable
fun MovieCard(
movie: Movie,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
AppCard(onClick = onClick, modifier = modifier) {
AppImage(url = movie.posterUrl, contentDescription = movie.title)
AppText(text = movie.title, style = MaterialTheme.typography.titleSmall)
AppText(text = movie.year.toString(), style = MaterialTheme.typography.bodySmall)
}
}
```
### Organism — composes molecules into a UI region
An organism is a screen section. It still accepts data as parameters — never reads from a ViewModel directly.
```kotlin
@Composable
fun MovieCatalogRow(
title: String,
movies: List<Movie>,
onMovieClick: (Movie) -> Unit,
modifier: Modifier = Modifier,
) {
Column(modifier = modifier) {
AppText(text = title, style = MaterialTheme.typography.headlineSmall)
LazyRow(
horizontalArrangement = Arrangement.spacedBy(LocalAppSpacing.current.sm)
) {
items(movies, key = { it.id }) { movie ->
MovieCard(movie = movie, onClick = { onMovieClick(movie) })
}
}
}
}
```
### Template — defines screen layout via slot composition
Templates define where content goes, not what it is. They accept slot parameters, no data.
```kotlin
@Composable
fun CatalogScreenTemplate(
topBar: @Composable () -> Unit,
hero: @Composable () -> Unit,
sections: @Composable () -> Unit,
modifier: Modifier = Modifier,
) {
Scaffold(topBar = topBar, modifier = modifier) { padding ->
LazyColumn(contentPadding = padding) {
item { hero() }
item { sections() }
}
}
}
```
### Level summary
| Level | Accepts | Composes | ViewModel? |
|-------|---------|----------|-----------|
| Atom | Primitives, slots, modifier | M3 components or raw Compose | No |
| Molecule | Data classes, callbacks, modifier | Atoms | No |
| Organism | Data, callbacks, modifier | Molecules + atoms | No |
| Template | Slots only, modifier | Scaffold + layout | No |
| Screen | ViewModel | Template + organisms + molecules | Yes — this is the only level that touches ViewModel |
---
## 5. The "Ask" Prompt
When the skill detects component-building intent (user asks to "build a card", "create a button",
"implement this component"), **before scaffolding code**, ask:
> "This looks like a **[molecule/organism]**. Should I also scaffold the **[lower-level] atoms**
> it needs, or does your codebase already have them?"
The developer can answer:
| Answer | Skill behavior |
|--------|---------------|
| "Yes, scaffold everything" | Create from token layer up — define spacing/color tokens, atoms, then the requested component |
| "Just build the card" | Build the requested component using atomic principles (slots, modifier, tokens) but don't create lower-level atoms |
| "We already have AppButton, AppImage" | Reuse those atoms, only build the new molecule/organism |
**The skill always applies atomic principles regardless of the answer.** The question is only
about whether to scaffold lower levels. Every component gets:
- `modifier: Modifier = Modifier`
- Slot APIs where appropriate
- Token-based styling (no hardcoded values)
- Sensible defaults
---
## 6. Anti-Patterns
| Anti-Pattern | Why It's Wrong | Fix |
|-------------|---------------|-----|
| `Color(0xFF1A73E8)` inside a composable body | Hardcoded color — not themeable, not dark-mode-safe | Use `MaterialTheme.colorScheme.*` or app brand token |
| `fontSize = 14.sp`, `fontWeight = FontWeight.Bold` | Hardcoded typography breaks consistency | Use `MaterialTheme.typography.*` |
| `Modifier.padding(16.dp)` without spacing token | Magic number spacing — inconsistent across app | Use `LocalAppSpacing.current.md` (or define a spacing scale) |
| Composable named `ButtonForSettings` / `CardWithRedBorder` | Named by context, not by function — not reusable | Name by what it IS: `AppButton`, `HighlightCard` |
| Public composable with no `modifier` parameter | Caller cannot control layout | Add `modifier: Modifier = Modifier`, pass to root element |
| Composable rendering UI with no slot parameters | Content is hardcoded, not composable | Add slot APIs for variable content |
| Raw `Text()` / `Button()` / `Icon()` in an organism | Skips atomic levels — loses theming and consistency | Use app-level atom wrappers |
| Organism that directly reads ViewModel | Couples UI to data layer — not reusable, not previewable | Accept data and callbacks as parameters; let the screen call ViewModel |
| Molecule with more than 3–4 responsibilities | Too much in one component — hard to reuse parts | Decompose into smaller molecules or extract atoms |
================================================
FILE: .claude/skills/compose-expert/references/auto-init.md
================================================
# Auto-Init: Compose Project Detection
Activate on `session_start`. Detect whether the current project uses Compose and
silently activate the skill with a brief announcement.
---
## Detection Gate
Run in order. Stop on first match.
### Step 1 — Gradle scan
Look for `build.gradle.kts`, `build.gradle`, or `libs.versions.toml` in the working
directory or one level up. Check file contents for any of:
- `compose`
- `androidx.compose`
- `org.jetbrains.compose`
- `compose-multiplatform`
```bash
# Check working directory and parent
for dir in . ..; do
for file in build.gradle.kts build.gradle libs.versions.toml; do
if [ -f "$dir/$file" ]; then
grep -qi "compose\|androidx\.compose\|org\.jetbrains\.compose\|compose-multiplatform" "$dir/$file" && echo "DETECTED" && break 2
fi
done
done
```
### Step 2 — Source scan fallback
If no Gradle file found or no Compose reference in Gradle, scan Kotlin source files.
```bash
# Find up to 10 .kt files (exclude build dirs), check for @Composable
find . -name "*.kt" -not -path "*/build/*" -print -quit 2>/dev/null | head -10 | \
xargs grep -l "@Composable" 2>/dev/null | head -1
```
If any file contains `@Composable`, detection succeeds.
---
## On Detection
Print one line:
```
Compose project detected — compose-expert skill active.
```
Then proceed normally — wait for the user's request and follow the standard workflow
in `SKILL.md`.
## On No Detection
Do nothing. The skill remains available if the user explicitly triggers it via
keyword (e.g., mentions `@Composable`, `LazyColumn`, `NavHost`, etc.) later in
the session.
================================================
FILE: .claude/skills/compose-expert/references/composition-locals.md
================================================
# CompositionLocals: Implicit Data Passing in Jetpack Compose
CompositionLocals provide a way to pass data implicitly down the composition tree without threading it through every function parameter. They're analogous to SwiftUI's `@Environment`.
## What Are CompositionLocals?
A CompositionLocal is a slot in the composition that holds a value accessible to any descendant composable without explicit parameter passing. Values are provided using `CompositionLocalProvider` and accessed via `current`.
```kotlin
val localAppTheme = compositionLocalOf { "Light" }
@Composable
fun MyScreen() {
CompositionLocalProvider(localAppTheme provides "Dark") {
DescendantComposable() // Can access "Dark" via localAppTheme.current
}
}
@Composable
fun DescendantComposable() {
Text(localAppTheme.current) // Reads "Dark"
}
```
**Source:** `androidx/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/CompositionLocal.kt`
## compositionLocalOf vs staticCompositionLocalOf
The key difference is **when recomposition is triggered** when a value changes.
### compositionLocalOf
Causes recomposition of all descendants when the value changes. Use when children genuinely depend on the value.
```kotlin
val LocalUserPreferences = compositionLocalOf { UserPreferences() }
```
**Recomposition behavior:** All consumers recompose.
### staticCompositionLocalOf
No recomposition of descendants; only the direct reader is affected. Use when you're **confident descendants don't depend on updates**, or updates are infrequent.
```kotlin
val LocalAppVersion = staticCompositionLocalOf { "1.0.0" }
```
**⚠️ Pitfall:** If a child reads `LocalAppVersion.current` and expects updates, you'll get stale data. Only use for truly static configuration.
### compositionLocalWithComputedDefaultOf
Introduced for computed default values. The lambda is called each time the value is read when no provider is active.
```kotlin
val LocalResources = compositionLocalWithComputedDefaultOf { context.resources }
```
This is more efficient than `compositionLocalOf { lazy { ... } }` because it avoids capturing state unnecessarily.
## Built-In CompositionLocals
The Compose runtime and UI libraries provide standard locals:
| Local | Type | Purpose |
|-------|------|---------|
| `LocalContext` | `Context` | Android Context (requires AndroidCompositionLocals) |
| `LocalConfiguration` | `Configuration` | Screen size, orientation, density |
| `LocalDensity` | `Density` | Pixel density for dp/px conversion |
| `LocalLayoutDirection` | `LayoutDirection` | LTR/RTL directionality |
| `LocalView` | `View` | Underlying Android View (if available) |
| `LocalLifecycleOwner` | `LifecycleOwner` | Activity/Fragment lifecycle |
| `LocalSavedStateRegistryOwner` | `SavedStateRegistryOwner` | For state persistence |
**Source:** `androidx/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidCompositionLocals.android.kt`
```kotlin
@Composable
fun MyComposable() {
val context = LocalContext.current
val density = LocalDensity.current
val config = LocalConfiguration.current
Text("Screen width: ${config.screenWidthDp}dp")
}
```
## Providing Values with CompositionLocalProvider
Provide one or multiple local values:
```kotlin
// Single local
CompositionLocalProvider(LocalUserPreferences provides user) {
Content()
}
// Multiple locals
CompositionLocalProvider(
LocalUserPreferences provides user,
LocalTheme provides darkTheme,
LocalLanguage provides "en"
) {
Content()
}
```
Values are **scoped** to descendants only:
```kotlin
CompositionLocalProvider(LocalUserPreferences provides userA) {
ComponentA() // Sees userA
CompositionLocalProvider(LocalUserPreferences provides userB) {
ComponentB() // Sees userB (overrides)
}
ComponentC() // Sees userA (original)
}
```
## Creating Custom CompositionLocals
Create locals at top level, outside composable functions:
```kotlin
data class AppTheme(val isDark: Boolean, val colors: Colors)
val LocalAppTheme = compositionLocalOf<AppTheme> {
error("AppTheme not provided")
}
// For nullable defaults
val LocalOptionalUser = compositionLocalOf<User?> { null }
```
**When to create a CompositionLocal:**
- Value is needed by many descendants
- Threading it as a parameter creates "prop drilling"
- Value is configuration-like (theme, locale, permissions)
**When NOT to use CompositionLocal:**
- Only 1–2 levels of composables need it → use parameters
- Value changes frequently and children need precise control → use State/ViewModel
- It's a dependency that should be testable → prefer parameters or dependency injection
## Testing with CompositionLocals
Provide test doubles to avoid real implementations:
```kotlin
@Composable
fun MyScreen() {
val user = LocalUserRepository.current
Text(user.name)
}
// In test
@Test
fun testMyScreen() {
composeRule.setContent {
CompositionLocalProvider(
LocalUserRepository provides FakeUserRepository(User("Test User"))
) {
MyScreen()
}
}
composeRule.onNodeWithText("Test User").assertExists()
}
```
## Anti-Patterns
### ✗ Using CompositionLocal as Generic Dependency Injection
```kotlin
// Bad: obscures dependencies, hard to test
val LocalEverything = compositionLocalOf { AppContainer() }
@Composable
fun MyScreen() {
val container = LocalEverything.current
val repo = container.userRepo
val cache = container.cache
}
```
**Better:** Provide specific locals or pass dependencies as parameters.
### ✗ Reading LocalContext Repeatedly
```kotlin
// Inefficient: reads on every recomposition
@Composable
fun MyComposable() {
val context = LocalContext.current // Reading repeatedly
// ...
}
```
**Better:** Read once outside the lambda or cache in remember:
```kotlin
@Composable
fun MyComposable() {
val context = LocalContext.current
val effect = remember(context) { /* use context */ }
}
```
### ✗ Storing Mutable State in CompositionLocal
```kotlin
// Bad: state changes won't trigger recomposition properly
val LocalCounter = compositionLocalOf { mutableStateOf(0) }
```
**Better:** Store the State in a parent composable and provide the value, not the State:
```kotlin
val LocalCount = compositionLocalOf { 0 }
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
CompositionLocalProvider(LocalCount provides count) {
Child()
}
}
```
## Key Takeaways
1. Use `compositionLocalOf` for values that children read and depend on updates
2. Use `staticCompositionLocalOf` only for truly static values
3. Prefer parameters over CompositionLocals unless you have significant nesting
4. Always provide a sensible error default or nullable type
5. Test by providing fake implementations via `CompositionLocalProvider`
6. CompositionLocals are not a replacement for proper architecture — use them for configuration and environment data, not general dependency injection
================================================
FILE: .claude/skills/compose-expert/references/deprecated-patterns.md
================================================
# Deprecated Patterns & API Migrations in Jetpack Compose
This guide covers major API changes and deprecations in Compose's evolution. Each section shows the old pattern → new approach with migration notes.
---
## String-Based Routes → Type-Safe `@Serializable` Routes
**Old (pre-2.8):**
```kotlin
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable("details/{id}") { backStackEntry ->
DetailsScreen(id = backStackEntry.arguments?.getString("id"))
}
}
```
**New (Navigation 2.8+):**
```kotlin
@Serializable data class Home
@Serializable data class Details(val id: String)
NavHost(navController, startDestination = Home) {
composable<Home> { HomeScreen() }
composable<Details> { backStackEntry ->
val args: Details = backStackEntry.toRoute()
DetailsScreen(id = args.id)
}
}
```
**Migration notes:** Type-safe routes eliminate string typos and runtime crashes. Requires `kotlinx-serialization` plugin and `navigation-compose:2.8.0+`. Encode complex objects using custom serializers.
---
## `accompanist-systemuicontroller` → `enableEdgeToEdge()`
**Old:**
```kotlin
val systemUiController = rememberSystemUiController()
systemUiController.setSystemBarsColor(
color = Color.Transparent,
darkIcons = false
)
```
**New (Compose 1.7+):**
```kotlin
enableEdgeToEdge()
// In Activity.onCreate() before setContent {}
```
**Migration notes:** Built-in since Compose 1.7. Automatically handles status bar, navigation bar, and IME behind content. Remove `accompanist-systemuicontroller` dependency entirely.
---
## `accompanist-pager` → `HorizontalPager`/`VerticalPager`
**Old:**
```kotlin
val pagerState = rememberPagerState()
HorizontalPager(count = items.size, state = pagerState) { page ->
PageContent(items[page])
}
```
**New (Foundation):**
```kotlin
val pagerState = rememberPagerState(pageCount = { items.size })
HorizontalPager(state = pagerState) { page ->
PageContent(items[page])
}
```
**Migration notes:** Native Pager in `foundation:1.6+` replaces accompanist. Removes external dependency. State initialization slightly different; pass lambda for dynamic page counts.
---
## `accompanist-swiperefresh` → `PullToRefreshBox`
**Old:**
```kotlin
SwipeRefresh(state = rememberSwipeRefreshState(isRefreshing), onRefresh = { load() }) {
LazyColumn { items(data) { item -> ItemRow(item) } }
}
```
**New (Material3):**
```kotlin
PullToRefreshBox(isRefreshing = isRefreshing, onRefresh = { load() }) {
LazyColumn { items(data) { item -> ItemRow(item) } }
}
```
**Migration notes:** `PullToRefreshBox` in `material3:1.2+` is the official replacement. Cleaner API. Remove `accompanist-swiperefresh` dependency.
---
## `accompanist-flowlayout` → `FlowRow`/`FlowColumn`
**Old:**
```kotlin
FlowRow(mainAxisSize = SizeMode.Expand) {
items.forEach { item -> Chip(text = item) }
}
```
**New (Foundation):**
```kotlin
FlowRow(modifier = Modifier.fillMaxWidth()) {
items.forEach { item -> Chip(text = item) }
}
```
**Migration notes:** FlowRow/FlowColumn in `foundation:1.6+`. API simplified; use standard modifiers instead of `SizeMode`. Better performance and less memory overhead.
---
## `LazyColumn { animateItemPlacement() }` → `LazyColumn { animateItem() }`
**Old:**
```kotlin
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item.name, Modifier.animateItemPlacement())
}
}
```
**New:**
```kotlin
LazyColumn {
items(items, key = { it.id }) { item ->
ItemRow(item.name, Modifier.animateItem())
}
}
```
**Migration notes:** `animateItem()` is the modern API (Compose 1.7+). Returns animation state for finer control. `animateItemPlacement()` still works but is superseded.
---
## `Modifier.composed` Pattern → `Modifier.Node` API
**Old:**
```kotlin
fun Modifier.myModifier(value: Int) = composed {
val state = remember { mutableStateOf(value) }
Modifier.fillMaxWidth().padding(8.dp)
}
```
**New:**
```kotlin
fun Modifier.myModifier(value: Int) = this.then(
Modifier
.fillMaxWidth()
.padding(8.dp)
)
// Or for complex state:
class MyModifierNode(val value: Int) : ModifierNodeElement<MyNodeImpl>() {
override fun create() = MyNodeImpl(value)
override fun update(node: MyNodeImpl) { node.value = value }
}
private class MyNodeImpl(var value: Int) : Modifier.Node
```
**Migration notes:** `composed {}` incurs overhead; avoid if no `remember` calls needed. For stateful modifiers, prefer `ModifierNode` API (Compose 1.8+). Benchmark before migrating existing code.
---
## Primitive State Optimization: `mutableStateOf(0)` → `mutableIntStateOf(0)`
**Old:**
```kotlin
var count by remember { mutableStateOf(0) }
var temperature by remember { mutableStateOf(37.5f) }
```
**New:**
```kotlin
var count by remember { mutableIntStateOf(0) }
var temperature by remember { mutableFloatStateOf(37.5f) }
```
**Migration notes:** Primitive-specific functions (`mutableIntStateOf`, `mutableFloatStateOf`, `mutableLongStateOf`) avoid boxing. Negligible performance impact in UI code but best practice since Compose 1.4+.
---
## `collectAsState()` → `collectAsStateWithLifecycle()`
**Old:**
```kotlin
val state by viewModel.uiState.collectAsState()
```
**New:**
```kotlin
val state by viewModel.uiState.collectAsStateWithLifecycle()
```
**Migration notes:** `collectAsStateWithLifecycle()` (Compose 1.6+) respects lifecycle—automatically stops collecting when activity is paused. Prevents memory leaks and redundant work. Requires `androidx.lifecycle:lifecycle-runtime-compose`.
---
## `@ExperimentalMaterial3Api` Graduation
**Old:**
```kotlin
@OptIn(ExperimentalMaterial3Api::class)
fun MyScreen() {
DatePicker(state = rememberDatePickerState())
}
```
**New (Compose 1.8+, Material3 1.3+):**
```kotlin
fun MyScreen() {
DatePicker(state = rememberDatePickerState())
}
```
**Migration notes:** DatePicker, TimePicker, ExposedDropdownMenuBox, and SearchBar graduated to stable in Material3 1.3+. Remove `@OptIn` annotations. APIs are stable—safe for production use.
---
## `Scaffold` Padding Enforcement
**Old (problematic):**
```kotlin
Scaffold(topBar = { TopAppBar() }) {
LazyColumn { items(data) { item -> ItemRow(item) } }
}
```
**New (required since 1.6+):**
```kotlin
Scaffold(topBar = { TopAppBar() }) { innerPadding ->
LazyColumn(modifier = Modifier.padding(innerPadding)) {
items(data) { item -> ItemRow(item) }
}
}
```
**Migration notes:** Must use `innerPadding` parameter since Compose 1.6. Ignoring it causes content overlap under system bars. The compiler enforces this now—old pattern won't compile.
---
## Material 2 → Material 3 Migration
**Old (Material):**
```kotlin
Button(onClick = { }) { Text("Click") }
TextField(value = text, onValueChange = { text = it })
Surface(color = MaterialTheme.colors.primary) { /* */ }
```
**New (Material3):**
```kotlin
Button(onClick = { }) { Text("Click") } // Same signature
TextField(value = text, onValueChange = { text = it }) // Same signature
Surface(color = MaterialTheme.colorScheme.primary) { /* */ }
```
**Migration notes:** Most Composables are API-compatible. Main changes: `colors` → `colorScheme`, new shape system, updated ripple defaults. Use Compose BOM to align Material3 versions.
---
## `WindowInsets` & Edge-to-Edge
**Old:**
```kotlin
Surface(modifier = Modifier.systemBarsPadding()) { /* */ }
```
**New (API 35+ default edge-to-edge):**
```kotlin
Surface(modifier = Modifier.padding(WindowInsets.systemBars.asPaddingValues())) { /* */ }
// Or use enableEdgeToEdge() in Activity—handles automatically
```
**Migration notes:** Edge-to-edge is default on Android 15+. System bar colors are managed by `enableEdgeToEdge()`. Use `WindowInsets.safeDrawing` for notch-aware layouts. Deprecate manual `systemBarsPadding()` calls.
---
## `ObservableState` Pattern Changes
**Old:**
```kotlin
@Composable
fun observe(state: ObservableState): State<T> = produceState(state.value) {
state.onChange { value = it }
}
```
**New:**
```kotlin
@Composable
fun <T> ObservableState<T>.asState(): State<T> = produceState(this.value) {
snapshotFlow { value }.collect { value = it }
}
```
**Migration notes:** `snapshotFlow {}` is preferred over direct listeners (Compose 1.6+). Integrates better with Compose's snapshot system. Use `distinctUntilChanged()` to avoid redundant recompositions.
================================================
FILE: .claude/skills/compose-expert/references/design-to-compose.md
================================================
# Design-to-Compose Translation Reference
Translating visual designs (Figma mockups, screenshots, wireframes) into production Compose code. This guide provides a systematic decomposition algorithm, property mapping tables, and patterns that produce clean, theme-aware, accessible composables on the first pass.
---
## 1. Composable Decomposition Algorithm
A divide-and-conquer approach for breaking any visual design into composable functions. Work top-down, outside-in.
### Step 1: Identify Root Layout Structure
Look at the full screen first. What is the outermost structural pattern?
| Visual Pattern | Compose Root |
|---|---|
| Top bar + content + bottom bar | `Scaffold` |
| Scrollable vertical content | `Column` + `verticalScroll()` or `LazyColumn` |
| Tabbed sections | `Scaffold` + `TabRow` + `HorizontalPager` |
| Full-bleed background with overlays | `Box` |
| Side drawer + content | `ModalNavigationDrawer` + `Scaffold` |
| Bottom sheet over content | `ModalBottomSheet` or `Scaffold` + `BottomSheetScaffold` |
### Step 2: Decompose into Visual Sections (Top-Down)
Scan the design from top to bottom. Draw horizontal lines between visually distinct sections. Each section becomes a composable or a block within the parent layout.
```
+---------------------------+
| Top App Bar | -> TopAppBar()
+---------------------------+
| Hero Image | -> HeroSection()
+---------------------------+
| Title + Subtitle | -> HeaderSection()
+---------------------------+
| Horizontal card list | -> FeaturedCardsRow()
+---------------------------+
| Vertical item list | -> ItemList()
+---------------------------+
| Bottom navigation | -> NavigationBar()
+---------------------------+
```
**Do:** Name sections by their purpose (`FeaturedCardsRow`), not their layout (`HorizontalScrollRow`).
**Don't:** Create a composable for every Figma frame. Flatten where possible.
### Step 3: For Each Section, Identify Layout Type
```
Is content stacked vertically?
└─ Yes → Column
└─ Is list dynamic/long? → LazyColumn
Is content arranged horizontally?
└─ Yes → Row
└─ Does it scroll? → LazyRow
└─ Does it wrap to next line? → FlowRow
Is content overlapping/layered?
└─ Yes → Box
Is it a grid?
└─ Fixed columns → LazyVerticalGrid
└─ Fixed item size → LazyVerticalStaggeredGrid
└─ Wrapping chips/tags → FlowRow
```
**Decision tree for layout selection:**
```
┌─ Overlapping layers? ──→ Box
│
Visual section ───┼─ Single axis? ──→ Vertical? ──→ Column / LazyColumn
│ └─ Horizontal? ──→ Row / LazyRow
│
└─ Grid / wrap? ──→ Fixed columns? ──→ LazyVerticalGrid
└─ Flowing tags? ──→ FlowRow
```
### Step 4: Extract Visual Properties
For each element, read these from the design:
- **Colors** — map to `MaterialTheme.colorScheme.*` tokens, not hex values
- **Typography** — map to `MaterialTheme.typography.*` text styles
- **Spacing** — padding and gaps in dp, map to theme spacing tokens
- **Elevation** — shadow depth, map to `tonalElevation` or `shadowElevation`
- **Corner radius** — map to `MaterialTheme.shapes.*`
### Step 5: Identify Interactive Elements and Map to M3 Components
| Visual Element | Compose M3 Component |
|---|---|
| Rounded rectangle with text + click | `Button` / `OutlinedButton` / `TextButton` |
| Card with image, title, subtitle | `Card` / `ElevatedCard` / `OutlinedCard` |
| Text input field | `TextField` / `OutlinedTextField` |
| Toggle switch | `Switch` |
| Checkbox | `Checkbox` / `TriStateCheckbox` |
| Chips / tags | `FilterChip` / `AssistChip` / `InputChip` / `SuggestionChip` |
| Floating action button | `FloatingActionButton` / `ExtendedFloatingActionButton` |
| Bottom navigation | `NavigationBar` + `NavigationBarItem` |
| Side navigation | `NavigationRail` / `NavigationDrawer` |
| Top bar | `TopAppBar` / `CenterAlignedTopAppBar` |
| Dialog / modal | `AlertDialog` / `BasicAlertDialog` |
| Progress indicator | `CircularProgressIndicator` / `LinearProgressIndicator` |
| Divider line | `HorizontalDivider` |
| Image with rounded corners | `Image` + `Modifier.clip()` |
| Dropdown menu | `ExposedDropdownMenuBox` |
| Slider | `Slider` / `RangeSlider` |
---
## 2. Figma-to-Compose Property Mapping Tables
### Layout Containers
| Figma Concept | Compose Equivalent |
|---|---|
| Frame (no auto-layout) | `Box` |
| Auto-layout Vertical | `Column` |
| Auto-layout Horizontal | `Row` |
| Auto-layout Wrap | `FlowRow` / `FlowColumn` |
| Grid (fixed columns) | `LazyVerticalGrid(columns = GridCells.Fixed(n))` |
| Absolute-positioned child | `Box` + `Modifier.offset(x, y)` or `Modifier.align()` |
| Component with variants | Composable function with parameters |
| Component instance | Function call site |
| Section / Group (organizational) | No composable needed; flatten into parent |
### Sizing Modes
| Figma Sizing | Compose Modifier |
|---|---|
| Fixed width/height | `Modifier.size(w.dp, h.dp)` or `.width(w.dp).height(h.dp)` |
| Hug contents | Default behavior (wrap content) -- no modifier needed |
| Fill container (horizontal) | `Modifier.fillMaxWidth()` |
| Fill container (vertical) | `Modifier.fillMaxHeight()` |
| Fill container (both) | `Modifier.fillMaxSize()` |
| Fill with min width | `Modifier.fillMaxWidth().widthIn(min = minW.dp)` |
| Fill with max width | `Modifier.fillMaxWidth().widthIn(max = maxW.dp)` |
| Fill with min/max height | `Modifier.fillMaxHeight().heightIn(min = ..., max = ...)` |
| Aspect ratio constraint | `Modifier.aspectRatio(ratio)` |
### Spacing Model
**Key principle: the parent owns spacing.**
| Figma Spacing | Compose Equivalent |
|---|---|
| Padding (all sides) | `Modifier.padding(all.dp)` on the container |
| Padding (per side) | `Modifier.padding(start = ..., top = ..., end = ..., bottom = ...)` |
| Gap between children (vertical auto-layout) | `Column(verticalArrangement = Arrangement.spacedBy(gap.dp))` |
| Gap between children (horizontal auto-layout) | `Row(horizontalArrangement = Arrangement.spacedBy(gap.dp))` |
| Space between (distribute) | `Arrangement.SpaceBetween` |
| Space around | `Arrangement.SpaceAround` |
**Do:** Use `start`/`end` instead of `left`/`right` for RTL language support.
```kotlin
// Correct: RTL-aware
Modifier.padding(start = 16.dp, end = 8.dp)
// Wrong: breaks in RTL locales
Modifier.padding(left = 16.dp, right = 8.dp) // Avoid
```
**Do:** Use `Arrangement.spacedBy()` for uniform gaps. Avoid inserting `Spacer` between every child.
```kotlin
// Do: clean and uniform
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text("First")
Text("Second")
Text("Third")
}
// Don't: manual spacers everywhere
Column {
Text("First")
Spacer(Modifier.height(12.dp))
Text("Second")
Spacer(Modifier.height(12.dp))
Text("Third")
}
```
### Shadow Mapping (Compose 1.9+)
Compose Foundation 1.9 introduced `dropShadow()` and `innerShadow()` as modifier extensions, replacing the legacy `shadow()` modifier for fine-grained control.
```kotlin
// Drop shadow: place BEFORE background in the modifier chain
Box(
Modifier
.dropShadow(
shape = RoundedCornerShape(12.dp),
color = Color.Black.copy(alpha = 0.15f),
blur = 8.dp,
offsetX = 0.dp,
offsetY = 4.dp,
spread = 0.dp
)
.background(Color.White, RoundedCornerShape(12.dp))
.padding(16.dp)
)
// Inner shadow: place AFTER background in the modifier chain
Box(
Modifier
.background(Color.White, RoundedCornerShape(12.dp))
.innerShadow(
shape = RoundedCornerShape(12.dp),
color = Color.Black.copy(alpha = 0.1f),
blur = 4.dp,
offsetX = 0.dp,
offsetY = 2.dp,
spread = 0.dp
)
.padding(16.dp)
)
```
**Figma shadow fields to Compose mapping:**
| Figma Shadow Property | Compose Parameter |
|---|---|
| X offset | `offsetX` |
| Y offset | `offsetY` |
| Blur | `blur` |
| Spread | `spread` |
| Color + opacity | `color = Color(hex).copy(alpha = opacity)` |
| Drop Shadow type | `Modifier.dropShadow()` |
| Inner Shadow type | `Modifier.innerShadow()` |
**Legacy approach** (pre-1.9, still valid for simple elevation shadows):
```kotlin
// Simple elevation shadow
Box(
Modifier
.shadow(elevation = 4.dp, shape = RoundedCornerShape(12.dp))
.background(Color.White)
)
```
### Gradient Mapping
| Figma Gradient Type | Compose Brush |
|---|---|
| Linear gradient | `Brush.linearGradient(colors, start, end)` |
| Radial gradient | `Brush.radialGradient(colors, center, radius)` |
| Angular/sweep gradient | `Brush.sweepGradient(colors, center)` |
Figma uses normalized coordinates (0.0 to 1.0). Convert to pixel `Offset` values:
```kotlin
// Figma linear gradient: start (0, 0) to end (1, 1), 45-degree diagonal
Box(
Modifier
.fillMaxWidth()
.height(200.dp)
.background(
Brush.linearGradient(
colors = listOf(Color(0xFF6200EE), Color(0xFF03DAC6)),
start = Offset.Zero,
end = Offset.Infinite // diagonal
)
)
)
// For precise Figma coordinates, use onSizeChanged or BoxWithConstraints:
BoxWithConstraints(
Modifier.background(
Brush.linearGradient(
colors = listOf(Color(0xFF6200EE), Color(0xFF03DAC6)),
start = Offset(0f, 0f),
end = Offset(constraints.maxWidth.toFloat(), constraints.maxHeight.toFloat())
)
)
) {
// content
}
// Radial gradient
Box(
Modifier
.size(200.dp)
.background(
Brush.radialGradient(
colors = listOf(Color.White, Color.Blue),
center = Offset(100f, 100f), // center of 200dp box (approx)
radius = 150f
)
)
)
```
### Corner Radius
| Figma Corner Radius | Compose Shape |
|---|---|
| All corners equal | `RoundedCornerShape(radius.dp)` |
| Per-corner values | `RoundedCornerShape(topStart = ..., topEnd = ..., bottomEnd = ..., bottomStart = ...)` |
| Fully rounded (pill) | `RoundedCornerShape(50)` or `CircleShape` |
| No radius | `RectangleShape` |
| Cut corners | `CutCornerShape(size.dp)` |
### Borders
```kotlin
// Solid border
Modifier.border(width.dp, Color(0xFFCCCCCC), RoundedCornerShape(8.dp))
// Gradient border
Modifier.border(
width = 2.dp,
brush = Brush.linearGradient(listOf(Color.Red, Color.Blue)),
shape = RoundedCornerShape(8.dp)
)
```
### Opacity
| Figma Property | Compose Equivalent |
|---|---|
| Layer opacity | `Modifier.alpha(0.5f)` |
| Fill color opacity | `Color(0xFF000000).copy(alpha = 0.5f)` |
| Blend mode | `Modifier.graphicsLayer { compositingStrategy = ... }` |
### Image Fill Modes
| Figma Image Mode | Compose ContentScale |
|---|---|
| Fill (cover, may crop) | `ContentScale.Crop` |
| Fit (contain, no crop) | `ContentScale.Fit` |
| Stretch (distort) | `ContentScale.FillBounds` |
| Tile | Custom `DrawScope` tiling |
| Fill width | `ContentScale.FillWidth` |
| Fill height | `ContentScale.FillHeight` |
```kotlin
Image(
painter = painterResource(R.drawable.hero),
contentDescription = "Hero image",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(12.dp))
)
```
---
## 3. Design Token to MaterialTheme Mapping
| Design System Token | MaterialTheme API |
|---|---|
| Color styles (Primary, Surface, Error...) | `MaterialTheme.colorScheme` |
| Text styles (Heading, Body, Caption...) | `MaterialTheme.typography` |
| Corner radius (Small, Medium, Large...) | `MaterialTheme.shapes` |
| Spacing scale (4, 8, 16, 24...) | Custom `CompositionLocal` (see below) |
| Elevation scale | Custom `CompositionLocal` (see below) |
### Custom Spacing CompositionLocal
Material 3 does not ship a spacing scale. Define one that mirrors your design system:
```kotlin
@Immutable
data class AppSpacing(
val xxs: Dp = 2.dp,
val xs: Dp = 4.dp,
val sm: Dp = 8.dp,
val md: Dp = 16.dp,
val lg: Dp = 24.dp,
val xl: Dp = 32.dp,
val xxl: Dp = 48.dp
)
val LocalAppSpacing = staticCompositionLocalOf { AppSpacing() }
// Provide in your theme wrapper
@Composable
fun AppTheme(content: @Composable () -> Unit) {
val spacing = AppSpacing()
CompositionLocalProvider(LocalAppSpacing provides spacing) {
MaterialTheme(
colorScheme = lightColorScheme(),
typography = Typography(),
shapes = Shapes()
) {
content()
}
}
}
// Usage at call site
@Composable
fun ProfileCard() {
val spacing = LocalAppSpacing.current
Card(
modifier = Modifier.padding(spacing.md)
) {
Column(
modifier = Modifier.padding(spacing.md),
verticalArrangement = Arrangement.spacedBy(spacing.sm)
) {
Text("Name", style = MaterialTheme.typography.titleMedium)
Text("Bio", style = MaterialTheme.typography.bodyMedium)
}
}
}
```
### Custom Elevation CompositionLocal
```kotlin
@Immutable
data class AppElevation(
val none: Dp = 0.dp,
val xs: Dp = 1.dp,
val sm: Dp = 2.dp,
val md: Dp = 4.dp,
val lg: Dp = 8.dp,
val xl: Dp = 16.dp
)
val LocalAppElevation = staticCompositionLocalOf { AppElevation() }
```
### Mapping Figma Text Styles to Typography
```kotlin
// Figma design system: Compose Typography:
// Heading/H1 36sp Bold → displaySmall or headlineLarge
// Heading/H2 28sp Bold → headlineMedium
// Heading/H3 22sp SemiBold→ titleLarge
// Body/Large 16sp Regular → bodyLarge
// Body/Small 14sp Regular → bodyMedium
// Caption 12sp Regular → bodySmall or labelMedium
// Button 14sp Medium → labelLarge
val AppTypography = Typography(
headlineLarge = TextStyle(
fontFamily = yourFontFamily,
fontWeight = FontWeight.Bold,
fontSize = 36.sp,
lineHeight = 44.sp
),
// ... map each Figma text style
)
```
---
## 4. Modifier Ordering Rules
**Canonical principle:** outer to inner = layout/sizing first, then decoration, then interaction.
Modifiers are applied left-to-right in the chain. Each modifier wraps everything that follows it. Think of it as layers from outside in.
### Correct Ordering Patterns
```kotlin
// Pattern 1: Card-like surface
Modifier
.fillMaxWidth() // 1. Layout sizing
.padding(horizontal = 16.dp, vertical = 8.dp) // 2. External margin (space from siblings)
.dropShadow( // 3. Shadow (before background)
shape = RoundedCornerShape(12.dp),
color = Color.Black.copy(alpha = 0.1f),
blur = 8.dp, offsetY = 4.dp
)
.background(Color.White, RoundedCornerShape(12.dp)) // 4. Background fill
.clip(RoundedCornerShape(12.dp)) // 5. Clip content to shape
.clickable { } // 6. Interaction (inside clip for ripple bounds)
.padding(16.dp) // 7. Internal padding (content inset)
// Pattern 2: Clickable with large touch target
Modifier
.fillMaxWidth()
.clickable { } // Clickable AFTER padding = larger touch target
.padding(16.dp) // Internal content padding
// Pattern 3: Background extends to edges, padding inside
Modifier
.fillMaxWidth()
.background(MaterialTheme.colorScheme.surface)
.padding(16.dp)
```
### Common Mistakes
```kotlin
// Wrong: padding before fillMaxWidth clips the fill area
Modifier
.padding(16.dp)
.fillMaxWidth() // Fills remaining width AFTER padding is applied
// Correct: fillMaxWidth first, then pad inward
Modifier
.fillMaxWidth()
.padding(16.dp)
// Wrong: clickable before padding = small touch target
Modifier
.clickable { }
.padding(16.dp) // Padding is outside the clickable area
// Correct: clickable after padding = padding area is clickable too
Modifier
.padding(16.dp)
.clickable { } // Entire padded region responds to clicks
// Wrong: shadow after background (invisible or clipped)
Modifier
.background(Color.White, RoundedCornerShape(12.dp))
.dropShadow(...) // Shadow drawn inside the background layer
// Correct: shadow before background
Modifier
.dropShadow(...)
.background(Color.White, RoundedCornerShape(12.dp))
```
---
## 5. Semantic vs Literal Translation
Figma designs express visual output. Compose code should express semantics. Always prefer Material 3 components over manually reconstructing their appearance with `Box` + modifiers.
### Anti-pattern: Literal Translation
```kotlin
// Figma shows a card: rounded rect, shadow, image, title, subtitle
// Literal translation -- DON'T
Box(
Modifier
.shadow(4.dp, RoundedCornerShape(12.dp))
.background(Color.White, RoundedCornerShape(12.dp))
.clip(RoundedCornerShape(12.dp))
) {
Column(Modifier.padding(16.dp)) {
Image(painter = painterResource(R.drawable.photo), contentDescription = null)
Text("Title", fontSize = 18.sp, fontWeight = FontWeight.Bold, color = Color.Black)
Text("Subtitle", fontSize = 14.sp, color = Color(0xFF666666))
}
}
```
### Correct: Semantic Translation
```kotlin
// Semantic translation -- DO
ElevatedCard(
modifier = Modifier.fillMaxWidth(),
shape = MaterialTheme.shapes.medium
) {
Column {
Image(
painter = painterResource(R.drawable.photo),
contentDescription = "Photo description",
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxWidth()
.height(180.dp)
)
Column(Modifier.padding(16.dp)) {
Text("Title", style = MaterialTheme.typography.titleMedium)
Text(
"Subtitle",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
```
### Why This Matters
| Concern | Literal Box+Modifiers | M3 Component |
|---|---|---|
| Dark theme | Breaks (hardcoded colors) | Automatic |
| Elevation overlay | Missing | Built-in tonal elevation |
| Ripple / feedback | Must add manually | Built-in |
| Accessibility | Must add semantics manually | Roles + descriptions built-in |
| Dynamic color (Material You) | Does not respond | Automatic |
| State handling (disabled, focused) | Manual | Built-in styling per state |
**Rule:** If a Material 3 component exists for the visual pattern, use it. Only build custom layouts for genuinely novel UI elements.
### Quick Mapping: Visual Pattern to M3 Component
| "It looks like a..." | Use |
|---|---|
| Rounded card with shadow | `ElevatedCard` |
| Outlined card | `OutlinedCard` |
| Pill-shaped button | `Button(shape = CircleShape)` |
| Icon + label row | `ListItem` |
| Search bar | `SearchBar` / `DockedSearchBar` |
| Segmented control | `SegmentedButton` (M3 1.2+) |
| Banner notification | `Snackbar` |
| Full-width separator | `HorizontalDivider` |
| Pull-to-refresh | `PullToRefreshBox` |
---
## 6. Anti-Patterns
### Over-nesting Layouts
Figma designs often have deep frame hierarchies for organizational reasons. Do not mirror this nesting in Compose -- flatten aggressively.
```kotlin
// Anti-pattern: mirroring Figma's 5-level nesting
Box {
Column {
Row {
Box {
Column {
Text("Title")
Text("Subtitle")
}
}
}
}
}
// Correct: flatten to what layout actually requires
Column {
Text("Title", style = MaterialTheme.typography.titleMedium)
Text("Subtitle", style = MaterialTheme.typography.bodyMedium)
}
```
Deep nesting increases measure/layout passes. Each layout node is a measure cost. Flatten to the minimum tree depth that achieves the visual result.
### Hardcoded Values vs Theme Tokens
```kotlin
// Anti-pattern: hardcoded hex colors and font sizes
Text(
text = "Hello",
color = Color(0xFF1A1A1A),
fontSize = 16.sp,
fontWeight = FontWeight.Medium
)
Box(Modifier.background(Color(0xFFF5F5F5)))
// Correct: theme tokens
Text(
text = "Hello",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface
)
Box(Modifier.background(MaterialTheme.colorScheme.surfaceVariant))
```
Hardcoded values break dark theme, dynamic color, and design system updates. The only acceptable hardcoded color is inside your theme definition files.
### Ignoring Accessibility
```kotlin
// Anti-pattern: no content description, tiny touch target
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = null, // Screen readers skip this
modifier = Modifier
.size(20.dp) // Below 48dp minimum touch target
.clickable { onFavorite() }
)
// Correct: accessible
IconButton(onClick = onFavorite) { // IconButton enforces 48dp minimum
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = "Add to favorites"
)
}
```
**Accessibility checklist for design translation:**
- All interactive elements have minimum 48dp touch targets (use `IconButton`, `TextButton`, or `Modifier.sizeIn(minWidth = 48.dp, minHeight = 48.dp)`)
- All images and icons have meaningful `contentDescription` (or `null` if purely decorative, paired with `Modifier.semantics { }` as needed)
- Color contrast ratios meet WCAG AA (4.5:1 for text, 3:1 for large text)
- Interactive states are visually distinguishable (not just color change)
### Designing for One Screen Width Only
```kotlin
// Anti-pattern: fixed widths that break on tablets
Row(Modifier.width(360.dp)) {
Column(Modifier.width(180.dp)) { /* left panel */ }
Column(Modifier.width(180.dp)) { /* right panel */ }
}
// Correct: responsive with WindowSizeClass
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
when (windowSizeClass.windowWidthSizeClass) {
WindowWidthSizeClass.COMPACT -> {
// Single column layout (phones)
Column { /* all content stacked */ }
}
WindowWidthSizeClass.MEDIUM -> {
// Two-pane list-detail (small tablets, foldables)
ListDetailPaneScaffold(/* ... */)
}
WindowWidthSizeClass.EXPANDED -> {
// Navigation rail + content (large tablets, desktop)
Row {
NavigationRail { /* ... */ }
Content(Modifier.weight(1f))
}
}
}
```
**Do:** Use `fillMaxWidth()`, `weight()`, and `WindowSizeClass` for responsive layouts.
**Don't:** Use fixed pixel/dp widths for containers that should adapt.
### Forgetting Scroll Behavior
```kotlin
// Anti-pattern: content overflows without scrolling
Column(Modifier.fillMaxSize()) {
// 20 items that exceed screen height -- bottom items invisible
repeat(20) { Text("Item $it") }
}
// Correct: add scrolling
Column(
Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
repeat(20) { Text("Item $it") }
}
// Or for dynamic lists:
LazyColumn(Modifier.fillMaxSize()) {
items(20) { Text("Item $it") }
}
```
### Ignoring Content Padding from Scaffold
```kotlin
// Anti-pattern: ignoring innerPadding from Scaffold
Scaffold(topBar = { TopAppBar(title = { Text("App") }) }) { innerPadding ->
// Content renders BEHIND the top bar
LazyColumn {
items(data) { Text(it) }
}
}
// Correct: apply innerPadding
Scaffold(topBar = { TopAppBar(title = { Text("App") }) }) { innerPadding ->
LazyColumn(
modifier = Modifier.padding(innerPadding), // or contentPadding = innerPadding
) {
items(data) { Text(it) }
}
}
```
================================================
FILE: .claude/skills/compose-expert/references/lists-scrolling.md
================================================
# Lists and Scrolling in Jetpack Compose
Efficient list rendering and scrolling are core to responsive mobile UIs. Jetpack Compose provides lazy layouts that compose items on-demand, not all at once.
## LazyColumn and LazyRow
These composables only compose visible items, making them efficient for large lists unlike `Column`/`Row` which compose all children upfront.
### LazyColumn (Vertical Scrolling)
```kotlin
LazyColumn(modifier = Modifier.fillMaxSize()) {
item {
HeaderComposable()
}
items(itemList.size) { index ->
ListItemComposable(itemList[index])
}
item {
FooterComposable()
}
}
```
### LazyRow (Horizontal Scrolling)
```kotlin
LazyRow(modifier = Modifier.fillMaxWidth()) {
items(imageList.size) { index ->
Image(
painter = painterResource(imageList[index]),
contentDescription = null,
modifier = Modifier.width(200.dp)
)
}
}
```
**Key difference from Column/Row:** Items are composed lazily as they enter the viewport, reducing memory and CPU usage.
**Source:** `androidx/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/`
## DSL Patterns: item, items, itemsIndexed
### `item` — Single Composable
```kotlin
LazyColumn {
item {
HeaderComposable()
}
}
```
### `items` — From a List or Count
```kotlin
// From a List
val users = listOf(User("Alice"), User("Bob"))
LazyColumn {
items(users) { user ->
UserCard(user)
}
}
// From a count
LazyColumn {
items(100) { index ->
Text("Item $index")
}
}
```
### `itemsIndexed` — With Index
```kotlin
LazyColumn {
itemsIndexed(users) { index, user ->
Text("${index + 1}. ${user.name}")
}
}
```
## Keys: Critical for Correctness and Performance
The `key` parameter ensures Compose can correctly identify and reuse items even if the list is reordered.
### ✓ Good: Stable Keys
```kotlin
data class User(val id: Long, val name: String)
LazyColumn {
items(users, key = { it.id }) { user ->
UserCard(user)
}
}
```
When `users` list is reordered, Compose knows which item moved because the key (id) is stable.
### ✗ Bad: Index as Key
```kotlin
// AVOID: If list is reordered, state gets mixed up
LazyColumn {
items(users, key = { index }) { user -> // Wrong!
var selected by remember { mutableStateOf(false) }
UserCard(user, selected)
}
}
```
If you remove item at index 0, the item that was at index 1 moves to index 0 and incorrectly inherits the state.
### ✗ Bad: No Key
```kotlin
// If list changes, item state/animations may misbehave
LazyColumn {
items(users) { user ->
UserCard(user)
}
}
```
Without a key, Compose can't distinguish items reliably if the list changes.
**Rule:** Always provide a stable, unique key when the list can change. Use IDs, not indices.
## Content Types for Recycling Optimization
Use `contentType` to enable layout reuse when rendering different item types:
```kotlin
sealed class ListItem
data class HeaderItem(val title: String) : ListItem()
data class UserItem(val user: User) : ListItem()
LazyColumn {
items(
items = listItems,
key = { it.id },
contentType = { when (it) {
is HeaderItem -> "header"
is UserItem -> "user"
}}
) { item ->
when (item) {
is HeaderItem -> HeaderComposable(item)
is UserItem -> UserCard(item)
}
}
}
```
Items with the same `contentType` can reuse layout state, improving performance when types repeat.
## LazyListState: Scroll Position and Animations
Manage scroll position programmatically:
```kotlin
val listState = rememberLazyListState()
LazyColumn(state = listState) {
items(100) { index ->
Text("Item $index")
}
}
// Scroll to item 50
LaunchedEffect(Unit) {
listState.scrollToItem(50)
}
// Animate scroll
LaunchedEffect(Unit) {
listState.animateScrollToItem(50)
}
// Read current scroll position
val firstVisibleIndex = listState.firstVisibleItemIndex
val firstVisibleOffset = listState.firstVisibleItemScrollOffset
```
**Use case:** Scroll to a newly added item, or scroll on user action.
## LazyVerticalGrid and LazyHorizontalGrid
### Fixed Columns
```kotlin
LazyVerticalGrid(columns = GridCells.Fixed(3)) {
items(itemList.size) { index ->
GridItemComposable(itemList[index])
}
}
```
### Adaptive Columns (Responsive)
```kotlin
// Column width ~100dp, fills available space with as many columns as fit
LazyVerticalGrid(columns = GridCells.Adaptive(100.dp)) {
items(itemList.size) { index ->
GridItemComposable(itemList[index])
}
}
```
Adaptive is preferable for responsive layouts.
## LazyVerticalStaggeredGrid
For Pinterest-style layouts with variable heights:
```kotlin
LazyVerticalStaggeredGrid(
columns = StaggeredGridCells.Fixed(2),
modifier = Modifier.fillMaxSize()
) {
items(images.size) { index ->
AsyncImage(
model = images[index].url,
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)
}
}
```
Items flow into the column with the shortest current height, creating a natural staggered appearance.
## HorizontalPager and VerticalPager
Page-by-page horizontal or vertical swiping:
```kotlin
val pagerState = rememberPagerState(pageCount = { pages.size })
HorizontalPager(state = pagerState) { page ->
PageComposable(pages[page])
}
// Programmatic scroll to page
LaunchedEffect(Unit) {
pagerState.scrollToPage(2)
}
// Animate to page
LaunchedEffect(Unit) {
pagerState.animateScrollToPage(2)
}
```
## Sticky Headers in Lazy Lists
Headers that remain visible at the top while scrolling:
```kotlin
LazyColumn {
stickyHeader {
SectionHeaderComposable("Section A")
}
items(itemsA) { item ->
ItemComposable(item)
}
stickyHeader {
SectionHeaderComposable("Section B")
}
items(itemsB) { item ->
ItemComposable(item)
}
}
```
## Nested Scrolling: Pitfalls
### ✗ Avoid Scrollable Inside LazyColumn
```kotlin
// Bad: nested scroll behavior is unpredictable
LazyColumn {
item {
LazyRow { // Nested lazy is OK, but...
items(innerList) { item ->
InnerItem(item)
}
}
}
}
```
Nested lazy composables are acceptable but require careful thought about scroll precedence.
### ✗ Avoid verticalScroll Modifier Inside LazyColumn
```kotlin
// Bad: two scroll containers fight for input
LazyColumn {
item {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text("This is scrollable twice!")
}
}
}
```
Don't wrap lazy children in scrollable modifiers; use nested lazy composables if you need multiple scroll axes.
### ✓ Use nestedScroll for Complex Scenarios
```kotlin
val scrollState = rememberScrollState()
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Custom scroll handling
return Offset.Zero
}
}
}
LazyColumn(modifier = Modifier.nestedScroll(nestedScrollConnection)) {
items(100) { index ->
Text("Item $index")
}
}
```
## Performance: Scroll-Dependent UI
### ✗ Bad: Heavy Computation in Item Lambda
```kotlin
LazyColumn {
items(users) { user ->
val processedData = expensiveComputation(user) // Runs every recomposition!
UserCard(user, processedData)
}
}
```
### ✓ Good: Use derivedStateOf for Scroll-Dependent Logic
```kotlin
val listState = rememberLazyListState()
val showScrollToTop = remember {
derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
LazyColumn(state = listState) {
items(100) { index ->
Text("Item $index")
}
}
if (showScrollToTop.value) {
Button(onClick = { /* scroll up */ }) { Text("Top") }
}
```
`derivedStateOf` derives a new value when scroll state changes without recomposing the entire list.
## Anti-Patterns
### ✗ Using LazyColumn for Small Fixed Lists
```kotlin
// Bad: overkill for 5 items
LazyColumn {
items(5) { index ->
Text("Item $index")
}
}
```
**Better:** Use `Column` for small fixed lists.
### ✗ No Keys + List Mutations
```kotlin
var items by remember { mutableStateOf(initialList) }
LazyColumn {
items(items) { item -> // No key!
ItemComposable(item, onDelete = {
items = items.filter { it.id != item.id }
})
}
}
```
Without keys, removing an item corrupts the state of remaining items.
### ✗ Creating New Objects in Keys
```kotlin
// Bad: key creates new object each recomposition
LazyColumn {
items(users, key = { User(it.id, it.name) }) { user ->
UserCard(user)
}
}
```
**Better:** Use primitive stable identifiers.
## Key Takeaways
1. Always provide stable, unique keys when using `items` on mutable lists
2. Use `contentType` for multi-type lists to enable layout reuse
3. Prefer `GridCells.Adaptive` for responsive grid layouts
4. Avoid nested scrollables; use `nestedScroll` for complex scroll behavior
5. Use `derivedStateOf` to avoid recomposing the entire list for scroll-dependent logic
6. `LazyColumn`/`LazyRow` are for large or unbounded lists; use `Column`/`Row` for small fixed lists
7. Never use indices as keys; list mutations will corrupt item state
### Production Crash Patterns
#### indexOf() Inside items {} — O(n^2) and Crashes
```kotlin
// BAD: O(n^2) total, returns -1 on recreated objects → IndexOutOfBoundsException
items(list.size) { index ->
val item = list[index]
val position = list.indexOf(item) // -1 if object was recreated!
}
// GOOD: use items() with key for stable identity
items(list, key = { it.id }) { item ->
ItemRow(item)
}
```
Root cause: `indexOf()` uses `equals()`. If list items are recreated (new object instances without proper `equals()` implementation), `indexOf()` returns -1.
#### Duplicate LazyColumn Keys
Backend sends items without unique IDs, or WebSocket reconnects deliver duplicates → `IllegalArgumentException: Key X was already used`.
```kotlin
// BAD: backend IDs may not be unique
items(messages, key = { it.id }) { msg -> ... }
// GOOD: add dedup index for safety
items(messages, key = { "${it.id}_${it.dedupIndex}" }) { msg -> ... }
```
The `dedupIndex` pattern: Add a field to the data class that is excluded from `equals()`/`hashCode()` but included in the LazyColumn key:
```kotlin
data class ChatMessage(
val id: String,
val text: String,
val timestamp: Long
) {
// NOT in data class constructor — excluded from equals/hashCode
var dedupIndex: Int = 0
}
// When processing messages from backend:
fun deduplicateKeys(messages: List<ChatMessage>): List<ChatMessage> {
val seen = mutableMapOf<String, Int>()
return messages.map { msg ->
val count = seen.getOrPut(msg.id) { 0 }
seen[msg.id] = count + 1
msg.also { it.dedupIndex = count }
}
}
```
#### derivedStateOf Driving Collection Size → IOOB
```kotlin
// BAD: derived count can be stale when items{} reads
val itemCount by remember { derivedStateOf { filterItems(allItems).size } }
LazyColumn {
items(itemCount) { index ->
val item = filterItems(allItems)[index] // IOOB if allItems changed!
}
}
// GOOD: derive the full filtered list, not just the count
val filteredItems by remember { derivedStateOf { filterItems(allItems) } }
LazyColumn {
items(filteredItems, key = { it.id }) { item ->
ItemRow(item)
}
}
```
Rule: `derivedStateOf` for scroll direction, visibility, form validation — **never for item counts that drive LazyList rendering**.
### LazyList Hardening
#### Multi-Field Keys with Collision Prefixes
When a LazyList mixes items from different data sources, IDs can collide:
```kotlin
// BAD: id=42 in both liveItems and archivedItems → crash
LazyColumn {
items(liveItems, key = { it.id }) { ... }
items(archivedItems, key = { it.id }) { ... }
}
// GOOD: prefix keys by source
LazyColumn {
items(liveItems, key = { "live_${it.id}" }) { ... }
items(archivedItems, key = { "archived_${it.id}" }) { ... }
items(pinnedItems, key = { "pinned_${it.id}" }) { ... }
}
```
#### items() with key Preferred Over itemsIndexed()
`items(list, key = { it.id })` gives each item a stable identity across recompositions. This enables:
- Correct `animateItem()` animations
- Efficient diffing (only changed items recompose)
- Proper state preservation per item
Use `itemsIndexed()` only when you genuinely need the index for display (e.g., numbered list).
#### animateItem() Parameters
```kotlin
items(items, key = { it.id }) { item ->
ItemRow(
item = item,
modifier = Modifier.animateItem(
fadeInSpec = tween(durationMillis = 250),
placementSpec = spring(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessMediumLow
),
fadeOutSpec = tween(durationMillis = 150)
)
)
}
```
#### ReportDrawnWhen for Startup Performance
Signal to the system that the first meaningful content is visible:
```kotlin
@Composable
fun ConversationListScreen(items: List<Conversation>) {
ReportDrawnWhen { items.isNotEmpty() }
LazyColumn {
items(items, key = { it.id }) { item ->
ConversationRow(item)
}
}
}
```
This improves Time To Initial Display (TTID) and Time To Full Display (TTFD) metrics in Android vitals.
#### Device-Specific Pagination
Some devices (notably Samsung) handle scroll events differently, requiring fewer scroll confirmations for lazy load triggers. When implementing infinite scroll, test on multiple OEMs and consider a configurable scroll threshold.
```kotlin
val shouldLoadMore by remember {
derivedStateOf {
val lastVisibleItem = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
val totalItems = listState.layoutInfo.totalItemsCount
lastVisibleItem >= totalItems - PREFETCH_THRESHOLD // e.g., 5
}
}
LaunchedEffect(shouldLoadMore) {
if (shouldLoadMore) { viewModel.loadNextPage() }
}
```
================================================
FILE: .claude/skills/compose-expert/references/material3-motion.md
================================================
# Material 3 Motion
Source: `compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/MotionTokens.kt`
and `compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/MotionScheme.kt`
in `androidx/androidx` (branch: `androidx-main`)
CMP compatibility: `MotionTokens`, `MotionScheme`, and all easing constants are in
`androidx.compose.material3` — available on all CMP targets (Android, Desktop, iOS, Web)
since M3 1.2.0. No platform guards needed.
---
## 1. Two APIs, One System
M3 provides two ways to apply motion:
| API | When to Use |
|-----|------------|
| **`MotionScheme`** (preferred) | Inside components that should adapt to the app's motion scheme — the theme controls whether spring-based or tween-based specs are used |
| **`MotionTokens` + `tween()`** | When you need explicit `tween()` / `keyframes {}` control and the component is not theme-motion-aware |
Use `MotionScheme` for new components. Use `MotionTokens` when the caller explicitly provides `AnimationSpec` parameters or when working with `AnimatedVisibility`, `Crossfade`, or shared elements.
---
## 2. MotionScheme API (Preferred for Components)
`MotionScheme` is part of `MaterialTheme` alongside `colorScheme`, `typography`, and `shapes`.
```kotlin
// Access via MaterialTheme
val motionScheme = MaterialTheme.motionScheme
// Two built-in schemes
MaterialTheme(motionScheme = MotionScheme.standard()) // utilitarian UI
MaterialTheme(motionScheme = MotionScheme.expressive()) // prominent UI (M3 recommended default)
```
### Spec Functions
| Function | Use Case |
|----------|---------|
| `defaultSpatialSpec<T>()` | Layout changes, position/size transitions (spatial) |
| `fastSpatialSpec<T>()` | Quick spatial transitions |
| `slowSpatialSpec<T>()` | Deliberate spatial transitions |
| `defaultEffectsSpec<T>()` | Opacity, color, non-spatial changes |
| `fastEffectsSpec<T>()` | Quick opacity/color transitions |
| `slowEffectsSpec<T>()` | Deliberate opacity/color transitions |
```kotlin
@Composable
fun AnimatedCard(expanded: Boolean) {
val motionScheme = MaterialTheme.motionScheme
// Size change = spatial
val size by animateDpAsState(
targetValue = if (expanded) 200.dp else 100.dp,
animationSpec = motionScheme.defaultSpatialSpec(),
label = "card-size"
)
// Color change = effects
val color by animateColorAsState(
targetValue = if (expanded) MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.surface,
animationSpec = motionScheme.defaultEffectsSpec(),
label = "card-color"
)
}
```
> **Key difference from `tween()`**: `MotionScheme` specs are spring-based by default in
> `expressive()`. The actual spec type (spring vs. tween) is controlled by the theme, not
> the component. This means motion adapts to the app's motion scheme without code changes.
---
## 3. Duration Tokens
Use when explicit `tween()` control is needed. All values sourced from `MotionTokens.kt`
(generated from Material Design spec v0_103). Durations are `Float` — use `.toInt()` for
`tween(durationMillis = ...)`.
| Token | Value (ms) | Use Case |
|-------|-----------|---------|
| `MotionTokens.DurationShort1` | 50ms | Micro interactions — ripple spread, checkbox tick |
| `MotionTokens.DurationShort2` | 100ms | Small element appear/disappear |
| `MotionTokens.DurationShort3` | 150ms | Icon transitions, selection indicators |
| `MotionTokens.DurationShort4` | 200ms | Tooltip appear, chip selection |
| `MotionTokens.DurationMedium1` | 250ms | FAB expand, card state change |
| `MotionTokens.DurationMedium2` | 300ms | **Most common** — dialog, bottom sheet, nav drawer |
| `MotionTokens.DurationMedium3` | 350ms | Expanded component transitions |
| `MotionTokens.DurationMedium4` | 400ms | Page-level panel transitions |
| `MotionTokens.DurationLong1` | 450ms | Complex layout changes |
| `MotionTokens.DurationLong2` | 500ms | Shared element enter |
| `MotionTokens.DurationLong3` | 550ms | Shared element — large content |
| `MotionTokens.DurationLong4` | 600ms | Full container morphs |
| `MotionTokens.DurationExtraLong1` | 700ms | Full-screen transitions only |
| `MotionTokens.DurationExtraLong2` | 800ms | Full-screen transitions only |
| `MotionTokens.DurationExtraLong3` | 900ms | Full-screen transitions only |
| `MotionTokens.DurationExtraLong4` | 1000ms | Full-screen transitions only |
---
## 4. Easing Tokens
All values sourced from `MotionTokens.kt`. Access via `MotionTokens.Easing*CubicBezier`.
| Token | CubicBezierEasing(x1, y1, x2, y2) | Direction | Use Case |
|-------|------------------------------------|-----------|---------|
| `MotionTokens.EasingEmphasizedDecelerateCubicBezier` | `(0.05f, 0.7f, 0.1f, 1.0f)` | Entering | Element arriving on screen — fast start, gentle settle |
| `MotionTokens.EasingEmphasizedAccelerateCubicBezier` | `(0.3f, 0.0f, 0.8f, 0.15f)` | Exiting | Element leaving screen — slow start, fast exit |
| `MotionTokens.EasingEmphasizedCubicBezier` | `(0.2f, 0.0f, 0.0f, 1.0f)` | Both | Default for most M3 component transitions |
| `MotionTokens.EasingStandardDecelerateCubicBezier` | `(0.0f, 0.0f, 0.0f, 1.0f)` | Entering | Simple enter — less expressive than Emphasized |
| `MotionTokens.EasingStandardAccelerateCubicBezier` | `(0.3f, 0.0f, 1.0f, 1.0f)` | Exiting | Simple exit |
| `MotionTokens.EasingStandardCubicBezier` | `(0.2f, 0.0f, 0.0f, 1.0f)` | Both | Simple state changes |
| `MotionTokens.EasingLinearCubicBezier` | `(0.0f, 0.0f, 1.0f, 1.0f)` | — | Looping / repeating animations only |
| `MotionTokens.EasingLegacyCubicBezier` | `(0.4f, 0.0f, 0.2f, 1.0f)` | — | `FastOutSlowInEasing` equivalent — do not use in new code |
| `MotionTokens.EasingLegacyAccelerateCubicBezier` | `(0.4f, 0.0f, 1.0f, 1.0f)` | — | `FastOutLinearInEasing` equivalent — do not use in new code |
| `MotionTokens.EasingLegacyDecelerateCubicBezier` | `(0.0f, 0.0f, 0.2f, 1.0f)` | — | `LinearOutSlowInEasing` equivalent — do not use in new code |
> **Enter/exit rule (always):** Enter = Decelerate easing (fast start, gentle settle).
> Exit = Accelerate easing (slow start, quick departure). Never use the same easing for both.
> The `Legacy*` tokens are equivalent to the pre-M3 named constants — do not use them in new code.
---
## 5. Using Tokens in Compose Animation APIs
### animate*AsState (prefer MotionScheme)
```kotlin
// Color — effects spec
val color by animateColorAsState(
targetValue = if (selected) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.surface,
animationSpec = MaterialTheme.motionScheme.defaultEffectsSpec(),
label = "selection-color"
)
// If explicit tween is required:
val color by animateColorAsState(
targetValue = targetColor,
animationSpec = tween(
durationMillis = MotionTokens.DurationShort4.toInt(), // 200ms
easing = MotionTokens.EasingStandardCubicBezier // state change, not enter/exit
),
label = "color"
)
```
### AnimatedVisibility (asymmetric enter/exit)
Enter and exit must use different durations and easing — exit is always faster.
```kotlin
AnimatedVisibility(
visible = visible,
enter = fadeIn(
animationSpec = tween(
durationMillis = MotionTokens.DurationMedium2.toInt(), // 300ms
easing = MotionTokens.EasingEmphasizedDecelerateCubicBezier // entering
)
) + slideInVertically(
animationSpec = tween(
durationMillis = MotionTokens.DurationMedium2.toInt(),
easing = MotionTokens.EasingEmphasizedDecelerateCubicBezier
),
initialOffsetY = { it / 4 }
),
exit = fadeOut(
animationSpec = tween(
durationMillis = MotionTokens.DurationShort4.toInt(), // 200ms — exit is faster
easing = MotionTokens.EasingEmphasizedAccelerateCubicBezier // exiting
)
) + slideOutVertically(
animationSpec = tween(
durationMillis = MotionTokens.DurationShort4.toInt(),
easing = MotionTokens.EasingEmphasizedAccelerateCubicBezier
),
targetOffsetY = { it / 4 }
)
) {
content()
}
```
### updateTransition (multi-property, shared spec)
```kotlin
val transition = updateTransition(targetState = expanded, label = "card")
val elevation by transition.animateDp(
transitionSpec = {
tween(
durationMillis = MotionTokens.DurationMedium1.toInt(), // 250ms
easing = MotionTokens.EasingEmphasizedCubicBezier
)
},
label = "elevation"
) { isExpanded -> if (isExpanded) 8.dp else 0.dp }
val cornerRadius by transition.animateDp(
transitionSpec = {
tween(
durationMillis = MotionTokens.DurationMedium1.toInt(),
easing = MotionTokens.EasingEmphasizedCubicBezier
)
},
label = "corner-radius"
) { isExpanded -> if (isExpanded) 0.dp else 12.dp }
```
### Shared element transitions
Shared elements cross screen boundaries — use Long range.
```kotlin
Modifier.sharedElement(
state = rememberSharedContentState(key = "hero-${item.id}"),
animatedVisibilityScope = animatedVisibilityScope,
boundsTransform = { _, _ ->
tween(
durationMillis = MotionTokens.DurationLong2.toInt(), // 500ms
easing = MotionTokens.EasingEmphasizedCubicBezier
)
}
)
```
---
## 6. Decision Tree
Pick the right duration by working through these questions in order:
1. **Micro interaction?** (ripple, checkbox tick, toggle thumb snap)
→ `DurationShort1` (50ms) or `DurationShort2` (100ms)
2. **Component state change?** (button press feedback, chip select, icon swap, tab indicator)
→ `DurationShort3` (150ms) or `DurationShort4` (200ms)
3. **Container change?** (card expand, FAB extend/shrink, menu open, tooltip)
→ `DurationMedium1` (250ms) or `DurationMedium2` (300ms) ← most common
4. **Screen-level element?** (dialog enter, bottom sheet slide, search bar expand, nav drawer)
→ `DurationMedium3` (350ms) or `DurationMedium4` (400ms)
5. **Shared element / hero transition?** (image or card expands from list to detail screen)
→ `DurationLong1` (450ms) or `DurationLong2` (500ms)
6. **Full-screen complex morph?** (entire screen layout changes)
→ `DurationLong3`–`DurationExtraLong1` (550ms–700ms)
**Easing rule (always apply):**
- Element arriving → `EasingEmphasizedDecelerateCubicBezier`
- Element departing → `EasingEmphasizedAccelerateCubicBezier`
- Element changing state (stays on screen) → `EasingEmphasizedCubicBezier`
- Looping/infinite → `EasingLinearCubicBezier`
- Prefer `MotionScheme` specs over manual easing for theme-aware components
---
## 7. Review Flags
Patterns to catch in code review. See also `references/pr-review.md` Category 3.
| Pattern in Code | Flag | Fix |
|----------------|------|-----|
| `tween(50)` | Hardcoded duration | `MotionTokens.DurationShort1.toInt()` |
| `tween(100)` | Hardcoded duration | `MotionTokens.DurationShort2.toInt()` |
| `tween(150)` | Hardcoded duration | `MotionTokens.DurationShort3.toInt()` |
| `tween(200)` | Hardcoded duration | `MotionTokens.DurationShort4.toInt()` |
| `tween(250)` | Hardcoded duration | `MotionTokens.DurationMedium1.toInt()` |
| `tween(300)` | Hardcoded duration | `MotionTokens.DurationMedium2.toInt()` |
| `tween(350)` | Hardcoded duration | `MotionTokens.DurationMedium3.toInt()` |
| `tween(400)` | Hardcoded duration | `MotionTokens.DurationMedium4.toInt()` |
| `tween(N)` with any integer literal | Hardcoded duration | Nearest `MotionTokens.Duration*` token |
| `FastOutSlowInEasing` | Pre-M3 easing | `MotionTokens.EasingEmphasizedCubicBezier` |
| `LinearOutSlowInEasing` | Pre-M3 easing | `MotionTokens.EasingEmphasizedDecelerateCubicBezier` |
| `FastOutLinearInEasing` | Pre-M3 easing | `MotionTokens.EasingEmphasizedAccelerateCubicBezier` |
| `animateColorAsState(target)` no `animationSpec` | Missing spec | `MaterialTheme.motionScheme.defaultEffectsSpec()` |
| Same easing on both `enter` and `exit` | Wrong pairing | Decelerate for enter, Accelerate for exit |
| Duration > 600ms on non-shared-element | Too slow | Reduce to `DurationLong1`–`DurationLong2` |
| New component uses explicit `tween()` instead of `MotionScheme` | Not theme-aware | Use `MaterialTheme.motionScheme.defaultSpatialSpec()` / `defaultEffectsSpec()` |
================================================
FILE: .claude/skills/compose-expert/references/modifiers.md
================================================
# Jetpack Compose Modifiers Reference
Modifiers are the primary way to decorate or augment a composable. They apply layout, drawing, gesture, and accessibility behavior. Understanding modifier ordering and the available APIs is critical for correctness and performance.
## Modifier Chain Ordering
Order matters. Modifiers are applied left-to-right in the DSL, but conceptually they wrap bottom-to-top. Each modifier receives a lambda that draws/measures the content below it.
```kotlin
// Example: different results depending on order
Box(
Modifier
.background(Color.Red)
.padding(16.dp)
.size(100.dp)
)
// Red background wraps the padded content, which wraps the 100x100 box
Box(
Modifier
.size(100.dp)
.padding(16.dp)
.background(Color.Red)
)
// 100x100 box is padded, then the whole thing (132x132) gets red background
```
**Do:** Order modifiers from outer (layout/sizing) to inner (styling/interaction).
**Don't:** Put `size` after `padding` if you want the padding included in the final size.
Source: `compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/Modifier.kt`
## Common Modifier Patterns
### Padding and Sizing
```kotlin
// Padding: external spacing around content
Box(Modifier.padding(16.dp)) { }
// Size: exact dimensions (overrides requested size from parent)
Box(Modifier.size(100.dp)) { }
Box(Modifier.size(width = 200.dp, height = 100.dp)) { }
// FillMaxWidth/FillMaxHeight: expand to available space
Box(Modifier.fillMaxWidth(0.8f)) { } // 80% of parent width
Box(Modifier.fillMaxSize()) { } // 100% of parent
// Do: use fillMaxWidth before adding padding for alignment clarity
Column(Modifier.fillMaxWidth()) {
Box(Modifier.padding(16.dp).fillMaxWidth()) { }
}
// Don't: apply fillMaxWidth after background if you want background to expand
// Instead:
Box(Modifier.fillMaxWidth().background(Color.Blue)) { }
```
### Background and Border
```kotlin
// Background applies a color to the surface
Box(Modifier.background(Color.Blue)) { }
Box(Modifier.background(Color.Blue, shape = RoundedCornerShape(8.dp))) { }
// Border draws a stroke (order matters!)
Box(
Modifier
.size(100.dp)
.border(2.dp, Color.Black, RoundedCornerShape(8.dp))
.background(Color.White)
)
// The border is drawn AFTER background in visual order (because modifiers below it are drawn first)
// Combine background + border: apply border first in chain
Box(
Modifier
.border(2.dp, Color.Black, RoundedCornerShape(8.dp))
.background(Color.White)
)
```
### Clipping
```kotlin
// Clip content to a shape
Box(Modifier.clip(RoundedCornerShape(8.dp))) {
Image(painter = painterResource(id = R.drawable.my_image), contentDescription = "")
}
// Do: apply clip before background if you want background inside the shape
Box(
Modifier
.clip(RoundedCornerShape(8.dp))
.background(Color.Blue)
) { }
// Don't: apply background then clip (works but semantically wrong)
Box(
Modifier
.background(Color.Blue)
.clip(RoundedCornerShape(8.dp))
) { }
```
## Clickable and Combined Clickable
```kotlin
// Basic click handling with ripple effect (Material 3 default)
Button(onClick = { }) { Text("Click me") }
// Manual clickable with ripple
Box(
Modifier
.size(100.dp)
.clickable(
indication = ripple(), // Material ripple feedback
interactionSource = remember { MutableInteractionSource() }
) { /* handle click */ }
)
// Combined clickable: long press + double click + click
Box(
Modifier
.combinedClickable(
onClick = { },
onLongClick = { },
onDoubleClick = { },
indication = ripple()
)
) { }
// Do: provide explicit interactionSource for testing/state observation
val interactionSource = remember { MutableInteractionSource() }
Box(
Modifier.clickable(
interactionSource = interactionSource,
indication = ripple()
) { }
)
// Don't: forget indication parameter (will have no visual feedback)
Box(Modifier.clickable { }) { } // No ripple
```
## Modifier.composed vs Modifier.Node
The old API (`composed`) is being phased out in favor of the new `ModifierNodeElement` API. Both work, but new code should use the latter.
### Old API: Modifier.composed
```kotlin
fun Modifier.myCustomModifier(value: String) = composed {
val state = remember { mutableStateOf(value) }
this.then(
Modifier
.background(Color.Blue)
.clickable { state.value = "updated" }
)
}
```
- Creates a new composable scope
- Captures composition locals
- Causes recomposition when remember state changes
- Deprecated but still supported
### New API: Modifier.Node
```kotlin
class MyCustomNode(val value: String) : Modifier.Node {
override fun onDetach() {
// Cleanup when removed
}
}
data class MyCustomElement(val value: String) : ModifierNodeElement<MyCustomNode>() {
override fun create() = MyCustomNode(value)
override fun update(node: MyCustomNode) {
node.value = value
}
}
fun Modifier.myCustomModifier(value: String) = this.then(MyCustomElement(value))
```
**Do:** Use `Modifier.Node` for new custom modifiers. It's more efficient and doesn't create composition scopes.
**Don't:** Create new `composed` modifiers; migrate existing ones to `Modifier.Node`.
Source: `compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/modifier/ModifierNodeElement.kt`
## Layout vs Drawing vs Pointer Input Modifiers
Modifiers fall into categories that affect when they execute:
```kotlin
// Layout modifier: affects measurement and layout pass
fun Modifier.customSize(width: Dp, height: Dp) =
this.then(object : LayoutModifier {
override fun MeasureScope.measure(measurable: Measurable, constraints: Constraints) =
measurable.measure(Constraints.fixed(width.roundToPx(), height.roundToPx()))
.run { layout(width = size.width, height = size.height) { place(0, 0) } }
})
// Drawing modifier: doesn't affect layout, just draws after content
fun Modifier.customDraw() = drawBehind { drawCircle(Color.Red) }
// Pointer input modifier: handles gestures/events
fun Modifier.detectCustomGesture() = pointerInput(Unit) {
detectTapGestures { offset -> /* handle */ }
}
```
**Do:** Use layout modifiers for sizing/positioning, drawing modifiers for visual effects, pointer modifiers for input.
**Don't:** Use layout modifiers to create visual effects; use drawing modifiers instead.
## Modifier.graphicsLayer — Performance Implications
`graphicsLayer` applies transformations at the graphics rendering level. It's more efficient than recomposing for animations.
```kotlin
// Efficient: transforms applied on the graphics layer, no recomposition
Box(
Modifier.graphicsLayer(
scaleX = 1.2f,
scaleY = 1.2f,
translationX = 10f,
rotationZ = 45f,
alpha = 0.8f
)
) { }
// Less efficient: recomposes every frame
var scaleX by remember { mutableStateOf(1f) }
LaunchedEffect(Unit) {
while (true) {
scaleX = 1.2f
delay(16)
}
}
Box(Modifier.scale(scaleX)) { }
```
**Do:** Use `graphicsLayer` for animations and frequent property changes.
**Don't:** Animate state values that trigger recomposition when `graphicsLayer` would suffice.
Source: `compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/GraphicsLayerModifier.kt`
## Modifier.semantics — Accessibility
Semantics describe the meaning of UI elements for screen readers and accessibility tests.
```kotlin
// Add semantic label
Button(onClick = { }) {
Icon(Icons.Default.Add, contentDescription = null)
Text("Add item")
}
// Custom semantic properties
Box(
Modifier
.size(100.dp)
.semantics {
contentDescription = "Custom box"
onClick(label = "Activate") { true }
}
) { }
// Do: always provide contentDescription for images
Image(
painter = painterResource(id = R.drawable.icon),
contentDescription = "User avatar"
)
// Don't: forget contentDescription (screen readers won't announce it)
Image(painter = painterResource(id = R.drawable.icon), contentDescription = null) // Wrong
```
Source: `compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/Semantics.kt`
## Modifier.testTag — UI Testing
```kotlin
// Add a test tag for finding composables in tests
Box(Modifier.testTag("my_box")) { }
// In tests:
composeTestRule.onNodeWithTag("my_box").performClick()
composeTestRule.onNodeWithTag("my_box").assertIsDisplayed()
```
**Do:** Use unique, descriptive test tags.
**Don't:** Use test tags in production code for business logic.
## Review Checklist: Modifier Ordering Bugs
### Hardcoded Size After Caller's `modifier` Parameter
When a composable accepts `modifier: Modifier = Modifier` and chains fixed `.height()` / `.width()` / `.size()` after it, caller size constraints are silently ignored or clamped.
```kotlin
// BAD: caller's height is outer constraint, component's 172.dp is inner — component always renders at 172dp
@Composable
fun BannerCard(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier // caller constraints applied first (outer)
.fillMaxWidth()
.height(172.dp) // inner — wins when smaller, clamped when larger
.clip(RoundedCornerShape(18.dp))
.background(Color.Green.copy(alpha = 0.08f)),
)
}
// Caller expects 200dp but gets 172dp:
BannerCard(modifier = Modifier.height(200.dp))
// Caller expects 100dp — component gets clamped/squished:
BannerCard(modifier = Modifier.height(100.dp))
```
**Why it happens:** Modifier chain resolves outer-to-inner (left-to-right). Outer constraint sets max bounds, inner constraint requests within those bounds. First size constraint wins as the ceiling.
**Fix option 1:** Component defaults first, caller can override via `.then(modifier)`:
```kotlin
// GOOD: component sets defaults, caller's modifier applied last and can override
@Composable
fun BannerCard(
modifier: Modifier = Modifier,
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(172.dp)
.clip(RoundedCornerShape(18.dp))
.background(Color.Green.copy(alpha = 0.08f))
.then(modifier), // caller can override size
)
}
```
**Fix option 2:** Use `defaultMinSize` for flexible sizing:
```kotlin
// GOOD: minimum guaranteed, caller can make it larger
@Composable
fun BannerCard(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier
.fillMaxWidth()
.defaultMinSize(minHeight = 172.dp) // floor, not ceiling
.clip(RoundedCornerShape(18.dp))
.background(Color.Green.copy(alpha = 0.08f)),
)
}
```
**Rule:** When a composable accepts `modifier: Modifier = Modifier`, never chain fixed `.height()` / `.width()` / `.size()` after the caller's modifier — caller constraints become outer bounds and the component's fixed size either ignores or gets clamped by them. Use `.then(modifier)` at the end or `defaultMinSize` for flexible sizing. Flag in every PR review.
---
## Anti-patterns
### Creating Modifiers in Composition
```kotlin
// Don't: creates a new Modifier every recomposition
@Composable
fun BadModifier() {
Box(Modifier.padding(16.dp).background(Color.Blue)) { }
}
// Do: extract to a variable or parameter
@Composable
fun GoodModifier(modifier: Modifier = Modifier) {
Box(modifier.padding(16.dp).background(Color.Blue)) { }
}
```
### Conditional Modifier Chains Done Wrong
```kotlin
// Don't: breaks type checking and readability
val mod = if (isSelected) Modifier.background(Color.Blue) else Modifier
Box(mod.padding(16.dp)) { }
// Do: use then() for conditional chaining
Box(
Modifier
.padding(16.dp)
.then(if (isSelected) Modifier.background(Color.Blue) else Modifier)
) { }
```
---
**Summary:** Master modifier ordering, prefer `Modifier.Node` over `composed`, use `graphicsLayer` for animations, and always consider the semantic layer for accessibility.
================================================
FILE: .claude/skills/compose-expert/references/multiplatform.md
================================================
# Compose Multiplatform (CMP) Reference
Reference: `compose-multiplatform` (JetBrains), `androidx.compose` (Google)
## CMP Architecture Overview
Compose Multiplatform uses a three-layer architecture:
### Layer 1: commonMain (Shared UI Runtime)
All Compose runtime, foundation, material3, and navigation APIs live here. Rendering uses Skia (via Skiko) on non-Android platforms, and the native Android Compose renderer on Android.
```kotlin
// commonMain/kotlin/App.kt
@Composable
fun App() {
MaterialTheme {
var count by remember { mutableIntStateOf(0) }
Button(onClick = { count++ }) {
Text("Clicked $count times")
}
}
}
```
This single composable renders natively on Android, Desktop (JVM), iOS, and WebAssembly.
### Layer 2: Platform Source Sets
Platform-specific code lives in `androidMain`, `desktopMain`, `iosMain`, `wasmJsMain`. Use `expect`/`actual` to bridge.
```
src/
commonMain/kotlin/ # Shared UI + logic
androidMain/kotlin/ # Android-specific (AndroidView, Context)
desktopMain/kotlin/ # Desktop-specific (Window, MenuBar)
iosMain/kotlin/ # iOS-specific (UIKitView, NSBundle)
wasmJsMain/kotlin/ # Web-specific (ComposeViewport)
```
### Layer 3: Platform Entry Points
Each platform has a different entry point to host the shared `App()` composable.
```kotlin
// androidMain — Standard Android Activity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { App() }
}
}
// desktopMain — JVM window
fun main() = application {
Window(
onCloseRequest = ::exitApplication,
title = "My App"
) {
App()
}
}
// iosMain — UIKit integration
fun MainViewController(): UIViewController =
ComposeUIViewController { App() }
// wasmJsMain — Browser canvas
@OptIn(ExperimentalComposeUiApi::class)
fun main() {
ComposeViewport(document.body!!) {
App()
}
}
```
---
## API Availability Matrix
| API | commonMain | Android | Desktop | iOS | Web |
|-----|:----------:|:-------:|:-------:|:---:|:---:|
| **Runtime** (`@Composable`, `remember`, `mutableStateOf`, `LaunchedEffect`, `derivedStateOf`) | Yes | Yes | Yes | Yes | Yes |
| **Foundation** (`Box`, `Column`, `Row`, `LazyColumn`, `TextField`, `Canvas`) | Yes | Yes | Yes | Yes | Yes |
| **Material3** (`Button`, `Card`, `Scaffold`, `TopAppBar`, `NavigationBar`) | Yes | Yes | Yes | Yes | Yes |
| **Navigation Compose** (type-safe `@Serializable` routes, `NavHost`) | Yes | Yes | Yes | Yes | Yes |
| **ViewModel** (`lifecycle-viewmodel-compose:2.10.0+`) | Yes | Yes | Yes | Yes | Yes |
| **`collectAsState()`** | Yes | Yes | Yes | Yes | Yes |
| **Compose Resources** (`Res.drawable.*`, `Res.string.*`, `Res.font.*`) | Yes | Yes | Yes | Yes | Yes |
| `AndroidView` | -- | Yes | -- | -- | -- |
| `BackHandler` | -- | Yes | -- | -- | -- |
| `dynamicColorScheme()` | -- | Yes | -- | -- | -- |
| `LocalContext` | -- | Yes | -- | -- | -- |
| `collectAsStateWithLifecycle()` | -- | Yes | -- | -- | -- |
| `hiltViewModel()` | -- | Yes | -- | -- | -- |
| Baseline Profiles / Macrobenchmark | -- | Yes | -- | -- | -- |
| `Window`, `MenuBar`, `Tray` | -- | -- | Yes | -- | -- |
| `DialogWindow` | -- | -- | Yes | -- | -- |
| `ComposePanel`, `SwingPanel` | -- | -- | Yes | -- | -- |
| Scrollbar composables | -- | -- | Yes | -- | -- |
| Keyboard shortcuts (`KeyShortcut`) | -- | -- | Yes | -- | -- |
| Desktop notifications | -- | -- | Yes | -- | -- |
| `UIKitView`, `UIKitViewController` | -- | -- | -- | Yes | -- |
| `ComposeUIViewController`, `ComposeUIView` | -- | -- | -- | Yes | -- |
| `PlatformImeOptions` | -- | -- | -- | Yes | -- |
| `LocalUIViewController`, `LocalUIView` | -- | -- | -- | Yes | -- |
| `ComposeViewport` | -- | -- | -- | -- | Yes |
| Browser history integration | -- | -- | -- | -- | Yes |
**Key insight:** The vast majority of the Compose API surface is available in `commonMain`. Platform-specific APIs exist for interop (embedding native views) and OS-level features (window management, system themes).
---
## expect/actual Patterns
### Pattern 1: isSystemInDarkTheme()
The theme detection API is `expect` in commonMain with platform-specific implementations.
```kotlin
// commonMain
@Composable
expect fun isSystemInDarkTheme(): Boolean
```
```kotlin
// skikoMain (Desktop, iOS, Web shared actual)
@Composable
actual fun isSystemInDarkTheme(): Boolean {
val systemTheme = LocalSystemTheme.current
return systemTheme == SystemTheme.Dark
}
```
```kotlin
// androidMain
@Composable
actual fun isSystemInDarkTheme(): Boolean {
val uiMode = LocalContext.current.resources.configuration.uiMode
return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
}
```
### Pattern 2: ResourceReader
The resource reading interface has four platform actuals because each platform loads files differently.
```kotlin
// commonMain
internal expect fun getPlatformResourceReader(): ResourceReader
interface ResourceReader {
suspend fun read(path: String): ByteArray
suspend fun readPart(path: String, offset: Long, size: Long): ByteArray
fun getUri(path: String): String
}
```
```kotlin
// androidMain — uses AssetManager
internal actual fun getPlatformResourceReader(): ResourceReader =
object : ResourceReader {
override suspend fun read(path: String): ByteArray =
context.assets.open(path).use { it.readBytes() }
override fun getUri(path: String): String =
"file:///android_asset/$path"
// ...
}
// desktopMain — uses ClassLoader
internal actual fun getPlatformResourceReader(): ResourceReader =
object : ResourceReader {
override suspend fun read(path: String): ByteArray =
this::class.java.classLoader!!.getResourceAsStream(path)!!.readBytes()
override fun getUri(path: String): String =
this::class.java.classLoader!!.getResource(path)!!.toURI().toString()
// ...
}
// iosMain — uses NSBundle + NSFileManager
internal actual fun getPlatformResourceReader(): ResourceReader =
object : ResourceReader {
override suspend fun read(path: String): ByteArray {
val nsPath = NSBundle.mainBundle.resourcePath + "/" + path
return NSFileManager.defaultManager.contentsAtPath(nsPath)!!.toByteArray()
}
// ...
}
// wasmJsMain — uses window.fetch()
internal actual fun getPlatformResourceReader(): ResourceReader =
object : ResourceReader {
override suspend fun read(path: String): ByteArray {
val response = window.fetch(path).await()
return response.arrayBuffer().await().toByteArray()
}
// ...
}
```
### Pattern 3: rememberResourceState (Sync vs Async)
JVM and iOS can load resources synchronously. JS/WASM must load asynchronously (returns default value first, then updates).
```kotlin
// JVM/iOS (skikoMain without web)
@Composable
internal actual fun <T> rememberResourceState(
key: Any,
getDefault: () -> T,
block: suspend () -> T
): State<T> {
// Can block briefly to load synchronously on first composition
return remember(key) { mutableStateOf(runBlocking { block() }) }
}
// wasmJsMain
@Composable
internal actual fun <T> rememberResourceState(
key: Any,
getDefault: () -> T,
block: suspend () -> T
): State<T> {
val state = remember(key) { mutableStateOf(getDefault()) }
LaunchedEffect(key) {
state.value = block() // Async update after initial render
}
return state
}
```
**Pitfall:** On WASM, resources loaded with `Res.*` may flash a default value before the actual resource loads. Design UIs to handle this gracefully (use placeholders or loading states).
### Pattern 4: Font Loading
Each platform uses its native font system, so font instantiation is platform-specific.
```kotlin
// commonMain
@Composable
expect fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font
// androidMain — Typeface from assets
@Composable
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
val typeface = remember(resource) { Typeface.createFromAsset(context.assets, resource.path) }
return AndroidFont(typeface, weight, style)
}
// desktopMain — java.awt.Font from classpath
// iosMain — CTFontCreateWithFontDescriptor from bundle
// wasmJsMain — FontFace API loaded via fetch
```
### Pattern 5: getSystemEnvironment
Returns locale, theme, and density information per platform.
```kotlin
// commonMain
internal expect fun getSystemEnvironment(): ResourceEnvironment
data class ResourceEnvironment(
val language: LanguageQualifier,
val region: RegionQualifier,
val theme: ThemeQualifier,
val density: DensityQualifier
)
// androidMain — reads from Configuration + DisplayMetrics
// desktopMain — reads from Locale.getDefault() + system theme detection
// iosMain — reads from NSLocale.currentLocale + UITraitCollection
// wasmJsMain — reads from navigator.language + matchMedia("(prefers-color-scheme: dark)")
```
---
## Resource System (Res.*)
### Directory Structure
Place resources under `commonMain/composeResources/`:
```
commonMain/
composeResources/
drawable/
icon.xml # Vector drawable (works on ALL platforms)
logo.png
drawable-dark/
icon.xml # Dark theme variant (auto-selected)
font/
roboto_regular.ttf
roboto_bold.ttf
values/
strings.xml # Default strings
values-fr/
strings.xml # French localization
files/
data.json # Raw files
```
### Usage
```kotlin
// Images
@Composable
fun AppLogo() {
Image(
painter = painterResource(Res.drawable.logo),
contentDescription = "App logo"
)
}
// Strings (with arguments)
@Composable
fun Greeting(name: String) {
Text(stringResource(Res.string.greeting, name))
}
// Fonts
@Composable
fun StyledText() {
val fontFamily = FontFamily(Font(Res.font.roboto_regular))
Text("Hello", fontFamily = fontFamily)
}
// Raw files
val bytes: ByteArray = Res.readBytes("files/data.json")
```
### Gotchas
**Lottie is NOT KMP-compatible.** The standard `com.airbnb.lottie:lottie-compose` only works on Android. For multiplatform Lottie animations, use:
- **Kottie** (`io.github.ismai117:kottie`) -- all CMP targets
- **Compottie** (`io.github.alexzhirkevich:compottie`) -- all CMP targets
**Multi-module font loading:** Fonts must be declared in the module that owns the `composeResources/` directory. In multi-module projects, place shared fonts in the top-level (app) module or create a shared resources module. Child modules cannot reference parent module resources directly.
**Android XML vectors work everywhere.** Android-format `VectorDrawable` XML files placed in `drawable/` render correctly on all platforms via the CMP Skia-based renderer. No conversion needed.
**Replace R.* with Res.*:**
```kotlin
// Android-only (will not compile in commonMain)
painterResource(R.drawable.icon)
stringResource(R.string.greeting)
// CMP (works in commonMain)
painterResource(Res.drawable.icon)
stringResource(Res.string.greeting)
```
---
## Migration from Android-Only to CMP
### Dependency Replacement Table
| Android-Only | CMP Replacement | Notes |
|-------------|-----------------|-------|
| Hilt (`hiltViewModel()`) | Koin (`koinViewModel()`) | Koin has first-class KMP support. Koin 4.0+ has Compose annotations. |
| Retrofit | Ktor Client | Ktor has multiplatform HTTP engines per platform. |
| Room | SQLDelight | SQLDelight generates Kotlin from SQL. Room KMP is experimental (2.7.0-alpha). |
| Coil 2.x | Coil 3.x KMP | Coil 3.0+ is fully multiplatform. Same API. |
| Lottie | Kottie / Compottie | See Lottie gotcha above. |
| `R.drawable.*`, `R.string.*` | `Res.drawable.*`, `Res.string.*` | Compose Resources replaces Android resources. |
| `collectAsStateWithLifecycle()` | `collectAsState()` | `collectAsState()` is available in commonMain. Lifecycle-awareness is Android-specific. |
| `BackHandler` | `expect`/`actual` | Implement back handling per platform. Desktop/iOS have different back concepts. |
| `LocalContext.current` | `expect`/`actual` | No universal replacement. Abstract platform needs behind an interface. |
### Top 5 Migration Pitfalls
**1. `LocalContext.current` sprinkled everywhere**
There is no KMP replacement for Android `Context`. Every usage must be audited and abstracted.
```kotlin
// Bad: Context usage scattered in composables
@Composable
fun ShareButton(text: String) {
val context = LocalContext.current // Android-only!
Button(onClick = {
val intent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, text) }
context.startActivity(intent)
}) { Text("Share") }
}
// Good: Abstract behind expect/actual
// commonMain
expect fun shareText(text: String)
// androidMain
actual fun shareText(text: String) {
val intent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, text) }
applicationContext.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
}
// iosMain
actual fun shareText(text: String) {
val controller = UIActivityViewController(listOf(text), null)
UIApplication.sharedApplication.keyWindow?.rootViewController
?.presentViewController(controller, true, null)
}
```
**2. Compose Compiler 2.0.0 incorrect stability inference on non-JVM targets**
The Compose compiler may incorrectly mark classes as unstable on iOS/WASM targets, causing excessive recomposition. If you see performance issues on non-Android targets:
```kotlin
// Explicitly annotate shared data classes
@Immutable
data class UiState(
val items: List<Item>,
val isLoading: Boolean
)
```
Always check compiler stability reports for all targets, not just Android.
**3. Don't migrate bottom-up -- start from the app module**
Migrating leaf modules first creates a broken build that stays broken for weeks. Instead:
1. Add KMP plugin to the app module
2. Move composables to `commonMain` one screen at a time
3. Create `expect`/`actual` stubs for platform dependencies
4. Migrate feature modules once the app module compiles
**4. `rememberSaveable` + `Bundle` + `@Parcelize` is Android-only**
`Bundle` and `@Parcelize` do not exist on non-Android targets. Use `@Serializable` with a custom `Saver` instead.
```kotlin
// Android-only (will not compile in commonMain)
@Parcelize
data class FormState(val name: String, val email: String) : Parcelable
var state by rememberSaveable { mutableStateOf(FormState("", "")) }
// CMP-compatible
@Serializable
data class FormState(val name: String, val email: String)
val formStateSaver = Saver<FormState, String>(
save = { Json.encodeToString(it) },
restore = { Json.decodeFromString(it) }
)
var state by rememberSaveable(stateSaver = formStateSaver) {
mutableStateOf(FormState("", ""))
}
```
**5. Version lockstep: Compose, Kotlin, Gradle, AGP**
CMP has strict version compatibility requirements. Mismatched versions produce cryptic compiler errors.
```kotlin
// build.gradle.kts -- versions must be compatible
plugins {
kotlin("multiplatform") version "2.1.20" // Kotlin version
id("org.jetbrains.compose") version "1.8.0" // CMP plugin version
id("org.jetbrains.kotlin.plugin.compose") version "2.1.20" // Must match Kotlin
}
// Check compatibility at:
// https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-compatibility-and-versioning.html
```
---
## Navigation in CMP
### Navigation Compose (Official -- Recommended)
The official `androidx.navigation:navigation-compose` is fully multiplatform as of Navigation 2.8.0+. Use `@Serializable` type-safe routes.
```kotlin
// commonMain -- works on all platforms
@Serializable
data object Home
@Serializable
data class Details(val id: Int)
@Composable
fun AppNavigation() {
val navController = rememberNavController()
NavHost(navController, startDestination = Home) {
composable<Home> {
HomeScreen(onItemClick = { id -> navController.navigate(Details(id)) })
}
composable<Details> { backStackEntry ->
val route = backStackEntry.toRoute<Details>()
DetailsScreen(itemId = route.id)
}
}
}
```
**Deep link handling differs per platform.** On Android, deep links integrate with `Intent` and `AndroidManifest.xml`. On other platforms, deep links must be wired manually (e.g., custom URL scheme on iOS via `application(_:open:options:)`, browser URL on Web).
### Navigation 3 (Experimental -- CMP 1.10+)
Navigation 3 is a ground-up redesign. `navigation3-common` is multiplatform, but `navigation3-ui` is not yet fully KMP.
```kotlin
// Available in CMP 1.10+ (experimental)
// navigation3-common: multiplatform
// navigation3-ui: limited platform support (check release notes)
```
Wait for stable releases before using in production multiplatform projects.
### Voyager (Third-Party)
Simple screen-based navigation. Good for small to medium apps.
```kotlin
// commonMain
class HomeScreen : Screen {
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
Button(onClick = { navigator.push(DetailsScreen(42)) }) {
Text("Go to Details")
}
}
}
class DetailsScreen(private val id: Int) : Screen {
@Composable
override fun Content() {
val screenModel = rememberScreenModel { DetailsScreenModel(id) }
// ScreenModel is Voyager's equivalent of ViewModel
Text("Details for $id")
}
}
// Entry point
Navigator(HomeScreen())
```
### Decompose (Third-Party)
Separates navigation logic from UI. Steeper learning curve but maximum testability and control. Navigation state is held in platform-agnostic `ComponentContext` objects.
```kotlin
// Navigation logic (pure Kotlin, no Compose dependency)
interface RootComponent {
val childStack: Value<ChildStack<*, Child>>
sealed class Child {
data class Home(val component: HomeComponent) : Child()
data class Details(val component: DetailsComponent) : Child()
}
}
// UI layer (Compose)
@Composable
fun RootContent(component: RootComponent) {
val childStack by component.childStack.subscribeAsState()
Children(childStack) { child ->
when (val instance = child.instance) {
is RootComponent.Child.Home -> HomeContent(instance.component)
is RootComponent.Child.Details -> DetailsContent(instance.component)
}
}
}
```
### Navigation Decision Guide
| Criteria | Navigation Compose | Voyager | Decompose |
|----------|:-----------------:|:-------:|:---------:|
| Official support | Yes (Google + JetBrains) | Community | Community |
| Learning curve | Low | Low | High |
| Type-safe routes | Yes (`@Serializable`) | Manual | Yes |
| Testability | Moderate | Moderate | High |
| All CMP targets | Yes | Yes | Yes |
| ViewModel integration | Yes (`lifecycle-viewmodel`) | ScreenModel | ComponentContext |
---
## Anti-Patterns
### Don't: Use hiltViewModel() in shared code
```kotlin
// Will not compile in commonMain -- Hilt is Android-only
@Composable
fun ProfileScreen() {
val viewModel: ProfileViewModel = hiltViewModel() // Android-only!
}
// Use lifecycle-viewmodel-compose (KMP) or Koin
@Composable
fun ProfileScreen() {
val viewModel = viewModel { ProfileViewModel() } // KMP ViewModel
// or
val viewModel = koinViewModel<ProfileViewModel>() // Koin KMP
}
```
### Don't: Use @Preview from the wrong package in commonMain
```kotlin
// Will not compile in commonMain
import androidx.compose.ui.tooling.preview.Preview // Android-only package!
@Preview
@Composable
fun MyPreview() { /* ... */ }
// CMP preview support varies by IDE and target
// Use Android Studio previews in androidMain only
// For Desktop, run the app directly (hot reload is fast)
```
### Don't: Use R.* in shared code
```kotlin
// Will not compile in commonMain
Image(painter = painterResource(R.drawable.icon), contentDescription = null)
Text(stringResource(R.string.title))
// Use Compose Resources
Image(painter = painterResource(Res.drawable.icon), contentDescription = null)
Text(stringResource(Res.string.title))
```
### Don't: Assume collectAsStateWithLifecycle exists in commonMain
```kotlin
// Will not compile in commonMain
val state by viewModel.uiState.collectAsStateWithLifecycle() // Android-only!
// Use collectAsState() instead -- available everywhere
val state by viewModel.uiState.collectAsState()
// collectAsState() is sufficient for CMP. The lifecycle awareness of
// collectAsStateWithLifecycle() is an Android optimization that stops
// collection when the app is backgrounded. On Desktop/Web this is
// unnecessary; on iOS, CMP handles lifecycle automatically.
```
================================================
FILE: .claude/skills/compose-expert/references/navigation.md
================================================
# Navigation in Jetpack Compose
Reference: `androidx/navigation/navigation-compose/src/commonMain/kotlin/androidx/navigation/compose/`
## Setup
### Basic NavHost and NavController
```kotlin
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "home" // Use Route::class with type-safe navigation
) {
composable<Home> {
HomeScreen(onNavigate = { navController.navigate(Details()) })
}
}
```
`rememberNavController()` creates a `NavController` that survives recomposition. Always use it in `NavHost`—never create `NavController` in a ViewModel.
## Type-Safe Navigation (Navigation 2.8+)
Use `@Serializable` route classes instead of string routes. This is the recommended pattern.
```kotlin
@Serializable
data class Home(val userId: String? = null)
@Serializable
data class Details(val itemId: Int)
NavHost(navController, startDestination = Home()) {
composable<Home> { backStackEntry ->
val args = backStackEntry.toRoute<Home>()
HomeScreen(userId = args.userId)
}
composable<Details> { backStackEntry ->
val args = backStackEntry.toRoute<Details>()
DetailsScreen(itemId = args.itemId)
}
}
```
Serialize complex types using `@Serializable` on nested data classes:
```kotlin
@Serializable
data class User(val id: Int, val name: String)
@Serializable
data class UserProfile(val user: User)
// Navigate:
navController.navigate(UserProfile(user = User(1, "Alice")))
```
## Declaring Destinations
### composable — Screen destinations
```kotlin
composable<Route> { backStackEntry ->
ScreenContent()
}
```
### dialog — Dialog destinations
```kotlin
dialog<Route> { backStackEntry ->
AlertDialog(...)
}
```
### navigation — Nested graphs (feature modules)
```kotlin
navigation<RootRoute>(startDestination = Home()) {
composable<Home> { HomeScreen() }
composable<Details> { DetailsScreen() }
}
```
## Navigating
### Navigate to a destination
```kotlin
// Type-safe
navController.navigate(Details(itemId = 42))
// Avoid: string-based navigation
navController.navigate("details/42") // Anti-pattern
```
### Pop back stack
```kotlin
navController.popBackStack()
// Pop with return value (save state before popping)
navController.previousBackStackEntry?.savedStateHandle?.set("key", value)
navController.popBackStack()
// In destination, retrieve:
val result = navController.currentBackStackEntry?.savedStateHandle?.get<T>("key")
```
### popUpTo — Clear back stack
```kotlin
// Navigate to Details, clearing Home from stack
navController.navigate(
Details(itemId = 42),
navOptions = navOptions {
popUpTo(Home::class) { inclusive = false }
}
)
// inclusive = true: Remove the target route too
navController.navigate(
Login(),
navOptions = navOptions {
popUpTo(Home::class) { inclusive = true }
}
)
// launchSingleTop: Reuse existing instance if already on stack
navController.navigate(
Details(itemId = 42),
navOptions = navOptions {
launchSingleTop = true
}
)
```
## Arguments and Back Stack Data
Compose Navigation handles serialization automatically with `@Serializable` routes.
### Passing complex data
```kotlin
@Serializable
data class Message(val id: Int, val text: String, val metadata: Metadata)
@Serializable
data class Metadata(val timestamp: Long, val priority: Int)
navController.navigate(Message(1, "Hello", Metadata(System.currentTimeMillis(), 1)))
```
### Result passing via SavedStateHandle
```kotlin
// Send result back
navController.previousBackStackEntry?.savedStateHandle?.set("result", "success")
navController.popBackStack()
// Receive in previous screen
val result = navController.currentBackStackEntry?.savedStateHandle?.get<String>("result")
```
## Nested Navigation Graphs
Organize related destinations into feature graphs.
```kotlin
navigation<FeatureRoot>(startDestination = FeatureHome()) {
composable<FeatureHome> { FeatureHomeScreen(onNext = { navController.navigate(FeatureDetail()) }) }
composable<FeatureDetail> { FeatureDetailScreen() }
}
```
Benefits: scoped ViewModels, separate back stack behavior, feature isolation.
## Deep Links
Declare deep links to open your app from URLs or notifications.
```kotlin
composable<Details>(
deepLinks = listOf(
navDeepLink<Details>(
uriPattern = "https://example.com/details/{itemId}"
)
)
) { backStackEntry ->
val args = backStackEntry.toRoute<Details>()
DetailsScreen(itemId = args.itemId)
}
// Navigate via deep link
navController.navigate("https://example.com/details/42")
```
Handle in `AndroidManifest.xml`:
```xml
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="example.com" />
</intent-filter>
</activity>
```
## Back Stack Management
### saveState / restoreState
Preserve screen state during navigation:
```kotlin
navController.navigate(
Details(itemId = 42),
navOptions = navOptions {
saveState = true
restoreState = true
}
)
```
### Check current route
```kotlin
val currentRoute = navController.currentBackStackEntry?.destination?.route
```
### Observe back stack
```kotlin
val backStackEntry by navController.currentBackStackEntryAsState()
val route = backStackEntry?.destination?.route
```
## Bottom Navigation Integration
```kotlin
var selectedItem by remember { mutableStateOf("home") }
val navController = rememberNavController()
Scaffold(
bottomBar = {
NavigationBar {
NavigationBarItem(
selected = selectedItem == "home",
onClick = {
selectedItem = "home"
navController.navigate(Home()) {
popUpTo(Home::class) { inclusive = true }
launchSingleTop = true
}
},
icon = { Icon(Icons.Default.Home, null) },
label = { Text("Home") }
)
NavigationBarItem(
selected = selectedItem == "profile",
onClick = {
selectedItem = "profile"
navController.navigate(Profile()) {
popUpTo(Home::class) { inclusive = false }
launchSingleTop = true
}
},
icon = { Icon(Icons.Default.Person, null) },
label = { Text("Profile") }
)
}
}
) {
NavHost(navController, startDestination = Home()) {
composable<Home> { HomeScreen() }
composable<Profile> { ProfileScreen() }
}
}
```
## Shared Element Transitions
```kotlin
NavHost(navController, startDestination = List()) {
composable<List>(
sharedTransitionSpec = {
SharedTransitionLayout()
}
) {
ListScreen()
}
composable<Detail>(
sharedTransitionSpec = {
SharedTransitionLayout()
}
) {
DetailScreen()
}
}
```
Use in screens:
```kotlin
Image(
painter = painterResource(id = R.drawable.image),
contentDescription = null,
modifier = Modifier.sharedBounds(
sharedContentState = rememberSharedContentState(key = "image"),
animatedVisibilityScope = this
)
)
```
## ViewModel Scoping with Navigation
Use `hiltViewModel()` to scope ViewModels to a back stack entry.
```kotlin
composable<Details> { backStackEntry ->
val viewModel: DetailsViewModel = hiltViewModel()
DetailsScreen(viewModel = viewModel)
}
```
ViewModels scoped this way survive configuration changes but are cleared when the back stack entry is removed.
## Testing Navigation
Use `TestNavHostController` to test navigation behavior.
```kotlin
@get:Rule
val composeTestRule = createComposeRule()
@Test
fun navigateToDetails() {
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
navController.navigatorProvider.addNavigator(ComposeNavigator())
composeTestRule.setContent {
NavHost(navController, startDestination = Home()) {
composable<Home> { HomeScreen(onNavigate = { navController.navigate(Details()) }) }
composable<Details> { DetailsScreen() }
}
}
composeTestRule.onNodeWithTag("detail_button").performClick()
assertEquals(Details::class.serializer().descriptor.serialName, navController.currentBackStackEntry?.destination?.route)
}
```
## Anti-Patterns
### Don't: Use string-based routes
```kotlin
// ❌ Anti-pattern
navController.navigate("details/42")
// ✅ Correct
navController.navigate(Details(itemId = 42))
```
### Don't: Create NavController in ViewModel
```kotlin
// ❌ Anti-pattern
class MyViewModel : ViewModel() {
val navController = NavController(context) // Wrong!
}
// ✅ Correct
// NavController lives in NavHost, injected into composables
```
### Don't: Navigate in composition
```kotlin
// ❌ Anti-pattern
@Composable
fun MyScreen() {
if (condition) {
navController.navigate(Details()) // Navigates on every recomposition!
}
}
// ✅ Correct
@Composable
fun MyScreen() {
LaunchedEffect(condition) {
if (condition) {
navController.navigate(Details())
}
}
}
```
### Don't: Mix navigation approaches
```kotlin
// ❌ Anti-pattern
navigation<Feature>(startDestination = "home") {
composable("home") { } // String-based
composable<Details> { } // Type-safe mixed with strings
}
// ✅ Correct
@Serializable
object FeatureHome
navigation<FeatureRoot>(startDestination = FeatureHome()) {
composable<FeatureHome> { }
composable<FeatureDetails> { }
}
```
================================================
FILE: .claude/skills/compose-expert/references/performance.md
================================================
# Performance Optimization Reference
## Three Phases: Composition, Layout, Drawing
Every frame consists of three phases. Understanding state reads in each phase prevents unnecessary recompositions.
### Composition Phase
- Executes composable functions, evaluates state reads
- Generates lambda and instance allocations
- **State reads here trigger recomposition** of the entire scope
### Layout Phase
- Calculates size and position, runs `measure` and `layout` blocks
- Can read state without triggering composition recomposition
- Mutable state reads OK; prefer `Modifier.offset { }` over `Modifier.offset()`
### Drawing Phase
- Emits draw operations, runs `Canvas` and custom `DrawScope`
- Cannot read mutable state without stability warnings
**Source**: `androidx/compose/runtime/Composer.kt`
---
## Recomposition Skipping with Compiler Reports
The Compose compiler generates `$changed` bitmasks to detect state changes. Enable compiler reports to inspect stability and skippability:
```kotlin
// build.gradle.kts
composeCompiler {
reportsDestination = layout.buildDirectory.dir("compose_reports")
metricsDestination = layout.buildDirectory.dir("compose_metrics")
}
```
After building (`./gradlew assembleRelease`), check the generated files in `build/compose_reports/`:
- **`*_composables.txt`** — shows each composable's restartability and skippability:
```
restartable skippable fun MyComponent(name: String, onClick: Function0<Unit>)
restartable fun UnstableComponent(items: List<Item>) // NOT skippable — unstable param
```
- **`*_classes.txt`** — shows stability inference for each class:
```
stable class User { stable val name: String }
unstable class ScreenState { unstable val items: List<Item> }
```
A composable missing `skippable` means the compiler cannot skip it during recomposition, even when inputs haven't changed. Fix by stabilizing its parameters (see Stability section below).
### Stability — @Stable and @Immutable
A type is **stable** if:
- Its public properties are stable
- Overrides to `equals()` and `hashCode()` are based on stable properties
- Recomposition is skipped when the same instance is passed
Mark stable types explicitly:
```kotlin
@Immutable
data class Person(val name: String, val age: Int)
@Stable
class UserViewModel : ViewModel {
private val _state = MutableState(UserState())
val state: State<UserState> = _state
}
// Composable receiving stable types can skip recomposition
@Composable
fun PersonCard(person: Person) {
Text(person.name) // Skips if person unchanged
}
```
**Avoid**: `@Stable` on data classes with mutable fields or non-final properties.
---
## Strong Skipping Mode (Default)
Android Gradle Plugin 8.0+ and Compose compiler 1.5.0+ enable **strong skipping mode**. This changes how lambdas are treated:
Without strong skipping, every lambda is unstable. With it enabled:
- Lambdas become stable if all captured variables are stable
- Fewer unnecessary recompositions
```kotlin
// With strong skipping: lambda is stable if count is stable
@Composable
fun Counter(count: Int) {
Button(onClick = { println(count) }) { // Stable lambda
Text("Count: $count")
}
}
```
Check `build.gradle.kts`:
```kotlin
android {
composeOptions {
kotlinCompilerExtensionVersion = "1.5.0"
}
}
```
---
## Defer State Reads to Layout/Draw Phase
Reading state in composition triggers recomposition. Push reads to later phases:
### Bad: Recomposition on Every Offset Change
```kotlin
@Composable
fun Box(offsetX: State<Float>) {
val x = offsetX.value // Reads in composition, triggers recomposition
Box(modifier = Modifier.offset(x.dp, 0.dp))
}
```
### Good: Deferred Read in Layout Phase
```kotlin
@Composable
fun Box(offsetX: State<Float>) {
Box(
modifier = Modifier.offset {
IntOffset(offsetX.value.toInt(), 0) // Read in layout phase
}
)
}
```
Use `Modifier.offset { }` (lambda) instead of `Modifier.offset()` (parameter) for state-dependent positioning.
---
## derivedStateOf — Reducing Recomposition Frequency
When deriving expensive computations from state, wrap in `derivedStateOf` to dedup recompositions:
```kotlin
// Bad: recomposes on every items change
@Composable
fun SearchResults(items: List<Item>, query: String) {
val filtered = items.filter { query in it.title } // Composition phase
LazyColumn {
items(filtered) { /* ... */ }
}
}
// Good: only recomposes if filtered result actually changes
@Composable
fun SearchResults(items: List<Item>, query: String) {
val filtered = remember(items, query) {
derivedStateOf { items.filter { query in it.title } }
}
LazyColumn {
items(filtered.value) { /* ... */ }
}
}
```
`derivedStateOf` deduplicates downstream recompositions — two different filters yielding the same list trigger only one downstream recomposition.
---
## remember with Keys
Avoid unnecessary recalculation:
```kotlin
// Recalculates on every recomposition
@Composable
fun ExpensiveItem(id: Int) {
val metadata = computeMetadata(id) // Called every time
Text(metadata)
}
// Recalculates only when id changes
@Composable
fun ExpensiveItem(id: Int) {
val metadata = remember(id) { computeMetadata(id) }
Text(metadata)
}
// Multiple keys
@Composable
fun Item(id: Int, userId: Int) {
val data = remember(id, userId) { fetchData(id, userId) }
Text(data.toString())
}
```
Omit `remember` if computation is cheap (string formatting, simple objects). Over-wrapping causes memory leaks.
---
## LazyList Performance — Keys and ContentType
### Always Provide Keys
Keys enable item reuse and animations:
```kotlin
// Bad: no keys, items recreated on every list change
LazyColumn {
items(users) { user ->
UserRow(user)
}
}
// Good: keys ena
gitextract_33wmcawt/
├── .claude/
│ ├── commands/
│ │ ├── bump-version.md
│ │ ├── ci-local.md
│ │ ├── dokka.md
│ │ └── spotless.md
│ └── skills/
│ ├── compose-expert/
│ │ ├── SKILL.md
│ │ └── references/
│ │ ├── accessibility.md
│ │ ├── animation.md
│ │ ├── atomic-design.md
│ │ ├── auto-init.md
│ │ ├── composition-locals.md
│ │ ├── deprecated-patterns.md
│ │ ├── design-to-compose.md
│ │ ├── lists-scrolling.md
│ │ ├── material3-motion.md
│ │ ├── modifiers.md
│ │ ├── multiplatform.md
│ │ ├── navigation.md
│ │ ├── performance.md
│ │ ├── platform-specifics.md
│ │ ├── pr-review.md
│ │ ├── production-crash-playbook.md
│ │ ├── side-effects.md
│ │ ├── source-code/
│ │ │ ├── cmp-source.md
│ │ │ ├── foundation-source.md
│ │ │ ├── material3-source.md
│ │ │ ├── navigation-source.md
│ │ │ ├── runtime-source.md
│ │ │ └── ui-source.md
│ │ ├── state-management.md
│ │ ├── styles-experimental.md
│ │ ├── theming-material3.md
│ │ ├── tv-compose.md
│ │ └── view-composition.md
│ ├── edge-to-edge/
│ │ └── SKILL.md
│ └── r8-analyzer/
│ ├── SKILL.md
│ └── references/
│ ├── CONFIGURATION.md
│ ├── KEEP-RULES-IMPACT-HIERARCHY.md
│ ├── REDUNDANT-RULES.md
│ ├── REFLECTION-GUIDE.md
│ └── android/
│ └── topic/
│ └── performance/
│ └── app-optimization/
│ └── enable-app-optimization.md
├── .editorconfig
├── .github/
│ └── workflows/
│ ├── build.yml
│ ├── publish.yml
│ └── setup/
│ ├── ios-setup/
│ │ └── action.yml
│ └── java-setup/
│ └── action.yml
├── .gitignore
├── CLAUDE.md
├── CONTRIBUTING.md
├── LICENSE
├── PUBLISHING.md
├── README.md
├── build.gradle.kts
├── docs/
│ ├── index.html
│ ├── jetlime/
│ │ ├── com.pushpal.jetlime/
│ │ │ ├── -event-point-animation/
│ │ │ │ ├── animation-spec.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── initial-value.html
│ │ │ │ └── target-value.html
│ │ │ ├── -event-point-type/
│ │ │ │ ├── -companion/
│ │ │ │ │ ├── -default.html
│ │ │ │ │ ├── -e-m-p-t-y.html
│ │ │ │ │ ├── custom.html
│ │ │ │ │ ├── filled.html
│ │ │ │ │ └── index.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── fill-percent.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── icon.html
│ │ │ │ ├── index.html
│ │ │ │ ├── is-custom.html
│ │ │ │ ├── is-empty-or-filled.html
│ │ │ │ ├── is-filled.html
│ │ │ │ ├── tint.html
│ │ │ │ └── type.html
│ │ │ ├── -event-position/
│ │ │ │ ├── -companion/
│ │ │ │ │ ├── dynamic.html
│ │ │ │ │ └── index.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── is-not-end.html
│ │ │ │ ├── is-not-start.html
│ │ │ │ └── name.html
│ │ │ ├── -horizontal-alignment/
│ │ │ │ ├── -b-o-t-t-o-m/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -t-o-p/
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ ├── -items-list/
│ │ │ │ ├── -items-list.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ └── items.html
│ │ │ ├── -jet-lime-column.html
│ │ │ ├── -jet-lime-defaults/
│ │ │ │ ├── column-style.html
│ │ │ │ ├── index.html
│ │ │ │ ├── line-gradient-brush.html
│ │ │ │ ├── line-solid-brush.html
│ │ │ │ └── row-style.html
│ │ │ ├── -jet-lime-event-defaults/
│ │ │ │ ├── event-style.html
│ │ │ │ ├── index.html
│ │ │ │ └── point-animation.html
│ │ │ ├── -jet-lime-event-style/
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── point-animation.html
│ │ │ │ ├── point-color.html
│ │ │ │ ├── point-fill-color.html
│ │ │ │ ├── point-placement.html
│ │ │ │ ├── point-radius.html
│ │ │ │ ├── point-stroke-color.html
│ │ │ │ ├── point-stroke-width.html
│ │ │ │ ├── point-type.html
│ │ │ │ ├── position.html
│ │ │ │ ├── set-point-placement.html
│ │ │ │ └── set-position.html
│ │ │ ├── -jet-lime-event.html
│ │ │ ├── -jet-lime-extended-event.html
│ │ │ ├── -jet-lime-row.html
│ │ │ ├── -jet-lime-style/
│ │ │ │ ├── content-distance.html
│ │ │ │ ├── equals.html
│ │ │ │ ├── hash-code.html
│ │ │ │ ├── index.html
│ │ │ │ ├── item-spacing.html
│ │ │ │ ├── line-brush.html
│ │ │ │ ├── line-horizontal-alignment.html
│ │ │ │ ├── line-thickness.html
│ │ │ │ ├── line-vertical-alignment.html
│ │ │ │ └── path-effect.html
│ │ │ ├── -local-jet-lime-style.html
│ │ │ ├── -point-placement/
│ │ │ │ ├── -c-e-n-t-e-r/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -e-n-d/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -s-t-a-r-t/
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ ├── -vertical-alignment/
│ │ │ │ ├── -l-e-f-t/
│ │ │ │ │ └── index.html
│ │ │ │ ├── -r-i-g-h-t/
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ └── index.html
│ │ └── package-list
│ ├── navigation.html
│ ├── scripts/
│ │ ├── main.js
│ │ ├── navigation-loader.js
│ │ ├── pages.json
│ │ ├── platform-content-handler.js
│ │ ├── prism.js
│ │ ├── safe-local-storage_blocking.js
│ │ └── sourceset_dependencies.js
│ └── styles/
│ ├── logo-styles.css
│ ├── main.css
│ ├── prism.css
│ └── style.css
├── dokkaModule.md
├── dokkaPackage.md
├── gradle/
│ ├── libs.versions.toml
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── jetlime/
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── consumer-rules.pro
│ ├── dokkaModule.md
│ ├── dokkaPackage.md
│ ├── jetlime.podspec
│ ├── proguard-rules.pro
│ └── src/
│ ├── androidMain/
│ │ └── AndroidManifest.xml
│ ├── androidTest/
│ │ └── java/
│ │ └── com/
│ │ └── pushpal/
│ │ └── jetlime/
│ │ ├── ExampleInstrumentedTest.kt
│ │ ├── JetLimeColumnTest.kt
│ │ └── JetLimeRowTest.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── pushpal/
│ │ └── jetlime/
│ │ ├── EventPointAnimation.kt
│ │ ├── EventPointType.kt
│ │ ├── EventPosition.kt
│ │ ├── ItemsList.kt
│ │ ├── JetLimeDefaults.kt
│ │ ├── JetLimeEvent.kt
│ │ ├── JetLimeEventDefaults.kt
│ │ ├── JetLimeEventStyle.kt
│ │ ├── JetLimeExtendedEvent.kt
│ │ ├── JetLimeList.kt
│ │ └── JetLimeStyle.kt
│ └── test/
│ └── java/
│ └── com/
│ └── pushpal/
│ └── jetlime/
│ └── ExampleUnitTest.kt
├── public-key.asc
├── sample/
│ ├── composeApp/
│ │ ├── build.gradle.kts
│ │ ├── composeApp.podspec
│ │ └── src/
│ │ ├── androidMain/
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin/
│ │ │ │ └── com/
│ │ │ │ └── pushpal/
│ │ │ │ └── jetlime/
│ │ │ │ └── sample/
│ │ │ │ ├── JetLimePreviews.kt
│ │ │ │ └── MainActivity.kt
│ │ │ └── res/
│ │ │ ├── drawable/
│ │ │ │ └── ic_launcher_background.xml
│ │ │ ├── drawable-v24/
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ ├── mipmap-anydpi-v26/
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── values/
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ └── values-night/
│ │ │ └── themes.xml
│ │ ├── commonMain/
│ │ │ ├── composeResources/
│ │ │ │ └── drawable/
│ │ │ │ ├── icon_change.xml
│ │ │ │ └── icon_check.xml
│ │ │ └── kotlin/
│ │ │ ├── App.kt
│ │ │ ├── Home.kt
│ │ │ ├── data/
│ │ │ │ └── Item.kt
│ │ │ ├── theme/
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Shape.kt
│ │ │ │ └── Theme.kt
│ │ │ └── timelines/
│ │ │ ├── BasicDashedTimeLine.kt
│ │ │ ├── BasicHorizontalTimeLine.kt
│ │ │ ├── BasicVerticalTimeLine.kt
│ │ │ ├── CustomizedHorizontalTimeLine.kt
│ │ │ ├── CustomizedVerticalTimeLine.kt
│ │ │ ├── ExtendedVerticalTimeLine.kt
│ │ │ ├── VerticalDynamicTimeLine.kt
│ │ │ └── event/
│ │ │ └── EventContent.kt
│ │ ├── desktopMain/
│ │ │ └── kotlin/
│ │ │ └── Main.kt
│ │ ├── iosMain/
│ │ │ └── kotlin/
│ │ │ └── MainViewController.kt
│ │ ├── jsMain/
│ │ │ ├── kotlin/
│ │ │ │ └── Main.kt
│ │ │ └── resources/
│ │ │ ├── index.html
│ │ │ └── styles.css
│ │ └── wasmJsMain/
│ │ ├── kotlin/
│ │ │ └── Main.kt
│ │ └── resources/
│ │ ├── index.html
│ │ └── styles.css
│ └── iosApp/
│ ├── Configuration/
│ │ └── Config.xcconfig
│ ├── Podfile
│ ├── Pods/
│ │ ├── Pods.xcodeproj/
│ │ │ ├── project.pbxproj
│ │ │ └── xcuserdata/
│ │ │ └── pushpalroy.xcuserdatad/
│ │ │ └── xcschemes/
│ │ │ ├── Pods-iosApp.xcscheme
│ │ │ └── xcschememanagement.plist
│ │ └── Target Support Files/
│ │ └── Pods-iosApp/
│ │ ├── Pods-iosApp-Info.plist
│ │ ├── Pods-iosApp-acknowledgements.markdown
│ │ ├── Pods-iosApp-acknowledgements.plist
│ │ ├── Pods-iosApp-dummy.m
│ │ ├── Pods-iosApp-umbrella.h
│ │ ├── Pods-iosApp.debug.xcconfig
│ │ ├── Pods-iosApp.modulemap
│ │ └── Pods-iosApp.release.xcconfig
│ ├── iosApp/
│ │ ├── Assets.xcassets/
│ │ │ ├── AccentColor.colorset/
│ │ │ │ └── Contents.json
│ │ │ ├── AppIcon.appiconset/
│ │ │ │ └── Contents.json
│ │ │ └── Contents.json
│ │ ├── ContentView.swift
│ │ ├── Info.plist
│ │ ├── Preview Content/
│ │ │ └── Preview Assets.xcassets/
│ │ │ └── Contents.json
│ │ └── iOSApp.swift
│ ├── iosApp.xcodeproj/
│ │ ├── project.pbxproj
│ │ └── xcuserdata/
│ │ └── pushpalroy.xcuserdatad/
│ │ └── xcschemes/
│ │ ├── iosApp.xcscheme
│ │ └── xcschememanagement.plist
│ └── iosApp.xcworkspace/
│ ├── contents.xcworkspacedata
│ ├── xcshareddata/
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata/
│ └── pushpalroy.xcuserdatad/
│ ├── UserInterfaceState.xcuserstate
│ └── xcschemes/
│ └── xcschememanagement.plist
├── scripts/
│ ├── add_git_tag.sh
│ ├── build_android.sh
│ ├── build_ios.sh
│ ├── build_macos.sh
│ ├── build_web_js.sh
│ ├── build_web_wasm.sh
│ ├── run_dokka.sh
│ └── run_spotless.sh
├── settings.gradle.kts
└── spotless/
└── copyright.kt
SYMBOL INDEX (800 symbols across 4 files)
FILE: docs/scripts/main.js
function t (line 1) | function t(e){for(var t=-1,r=0;r<n.length;r++)if(n[r].identifier===e){t=...
function r (line 1) | function r(e,r){for(var i={},a=[],l=0;l<e.length;l++){var c=e[l],s=r.bas...
function o (line 1) | function o(e,n){var t=n.domAPI(n);t.update(e);return function(n){if(n){i...
function a (line 1) | function a(n){return function(){i.nextExpectedAction=n,++i.sequenceLevel...
function l (line 1) | function l(n){var a;i.fireCallback(r,n,e),"keyup"!==o&&(a=t(3970),i.igno...
function n (line 1) | function n(e,n,t,r){return!e.addEventListener&&(n="on"+n),(e.addEventLis...
function j (line 1) | function j(e,n,t,r){for(var o,i=l(e);(o=i.next())&&!o.done;)if(N(n,o.val...
function z (line 1) | function z(e){return void 0===e?null:"object"!=typeof e?"symbol"!=typeof...
function D (line 1) | function D(e,n,t,o,i,a){var l=z(t);if(null!=l)return l;var c=k(n,l),s=r(...
function R (line 1) | function R(e,n,t){var r=z(t);return null!=r?r:T(n,r)&&!T(e,r)}
function M (line 1) | function M(e,n,t,r,o,i){for(var a,c,s=l(e);(a=s.next())&&!a.done;)if(N(t...
function N (line 1) | function N(e,n,t,o){var a=t||{};if(a.strict?s(e,n):e===n)return!0;if(b(e...
function F (line 1) | function F(e){return!(!e||"object"!=typeof e||"number"!=typeof e.length)...
function z (line 6) | function z(e,n){if(e===1/0||e===-1/0||e!=e||e&&e>-1e3&&e<1e3||C.call(/e/...
function L (line 6) | function L(e,n,t){var r=t.quoteStyle||n,o=N[r];return o+e+o}
function H (line 6) | function H(e){return m.call(String(e),/"/g,""")}
function U (line 6) | function U(e){return!T||!("object"==typeof e&&(T in e||void 0!==e[T]))}
function W (line 6) | function W(e){return"[object Array]"===$(e)&&U(e)}
function G (line 6) | function G(e){return"[object RegExp]"===$(e)&&U(e)}
function Y (line 6) | function Y(e){if(P)return e&&"object"==typeof e&&e instanceof Symbol;if(...
function F (line 6) | function F(n,t,r){if(t&&(l=S.call(l)).push(t),r){var i={depth:c.depth};r...
function V (line 6) | function V(e,n){return q.call(e,n)}
function $ (line 6) | function $(e){return h.call(e)}
function K (line 6) | function K(e,n){if(e.indexOf)return e.indexOf(n);for(var t=0,r=e.length;...
function Q (line 6) | function Q(e,n){if(e.length>n.maxStringLength){var t=e.length-n.maxStrin...
function X (line 6) | function X(e){var n=e.charCodeAt(0),t={8:"b",9:"t",10:"n",12:"f",13:"r"}...
function Z (line 6) | function Z(e){return"Object("+e+")"}
function J (line 6) | function J(e){return e+" { ? }"}
function ee (line 6) | function ee(e,n,t,r){return e+" ("+n+") {"+(r?ne(t,r):x.call(t,", "))+"}"}
function ne (line 6) | function ne(e,n){if(0===e.length)return"";var t="\n"+n.prev+n.base;retur...
function te (line 6) | function te(e,n){var t=W(e),r=[];if(t){r.length=e.length;for(var o=0;o<e...
function o (line 6) | function o(){}
function i (line 6) | function i(){}
function e (line 6) | function e(e,n,t,o,i,a){if(a!==r){var l=new Error("Calling PropTypes val...
function n (line 6) | function n(){return e}
function a (line 14) | function a(e){for(var n="https://reactjs.org/docs/error-decoder.html?inv...
function s (line 14) | function s(e,n){u(e,n),u(e+"Capture",n)}
function u (line 14) | function u(e,n){for(c[e]=n,e=0;e<n.length;e++)l.add(n[e])}
function A (line 14) | function A(e,n,t,r,o,i,a){this.acceptsBooleans=2===n||3===n||4===n,this....
function m (line 14) | function m(e){return e[1].toUpperCase()}
function y (line 14) | function y(e,n,t,r){var o=b.hasOwnProperty(n)?b[n]:null;(null!==o?0===o....
function U (line 14) | function U(e){return null===e||"object"!=typeof e?null:"function"==typeo...
function W (line 14) | function W(e){if(void 0===L)try{throw Error()}catch(e){var n=e.stack.tri...
function Y (line 14) | function Y(e,n){if(!e||G)return"";G=!0;var t=Error.prepareStackTrace;Err...
function q (line 14) | function q(e){switch(e.tag){case 5:return W(e.type);case 16:return W("La...
function V (line 14) | function V(e){if(null==e)return null;if("function"==typeof e)return e.di...
function $ (line 14) | function $(e){switch(typeof e){case"boolean":case"number":case"object":c...
function K (line 14) | function K(e){var n=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase...
function Q (line 14) | function Q(e){e._valueTracker||(e._valueTracker=function(e){var n=K(e)?"...
function X (line 14) | function X(e){if(!e)return!1;var n=e._valueTracker;if(!n)return!0;var t=...
function Z (line 14) | function Z(e){if(void 0===(e=e||("undefined"!=typeof document?document:v...
function J (line 14) | function J(e,n){var t=n.checked;return o({},n,{defaultChecked:void 0,def...
function ee (line 14) | function ee(e,n){var t=null==n.defaultValue?"":n.defaultValue,r=null!=n....
function ne (line 14) | function ne(e,n){null!=(n=n.checked)&&y(e,"checked",n,!1)}
function te (line 14) | function te(e,n){ne(e,n);var t=$(n.value),r=n.type;if(null!=t)"number"==...
function re (line 14) | function re(e,n,t){if(n.hasOwnProperty("value")||n.hasOwnProperty("defau...
function oe (line 14) | function oe(e,n,t){"number"===n&&Z(e.ownerDocument)===e||(null==t?e.defa...
function ie (line 14) | function ie(e,n){return e=o({children:void 0},n),(n=function(e){var n=""...
function ae (line 14) | function ae(e,n,t,r){if(e=e.options,n){n={};for(var o=0;o<t.length;o++)n...
function le (line 14) | function le(e,n){if(null!=n.dangerouslySetInnerHTML)throw Error(a(91));r...
function ce (line 14) | function ce(e,n){var t=n.value;if(null==t){if(t=n.children,n=n.defaultVa...
function se (line 14) | function se(e,n){var t=$(n.value),r=$(n.defaultValue);null!=t&&((t=""+t)...
function ue (line 14) | function ue(e){var n=e.textContent;n===e._wrapperState.initialValue&&""!...
function de (line 14) | function de(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";ca...
function ge (line 14) | function ge(e,n){return null==e||"http://www.w3.org/1999/xhtml"===e?de(n...
function ve (line 14) | function ve(e,n){if(n){var t=e.firstChild;if(t&&t===e.lastChild&&3===t.n...
function Ee (line 14) | function Ee(e,n,t){return null==n||"boolean"==typeof n||""===n?"":t||"nu...
function Ce (line 14) | function Ce(e,n){for(var t in e=e.style,n)if(n.hasOwnProperty(t)){var r=...
function xe (line 14) | function xe(e,n){if(n){if(we[e]&&(null!=n.children||null!=n.dangerouslyS...
function Se (line 14) | function Se(e,n){if(-1===e.indexOf("-"))return"string"==typeof n.is;swit...
function _e (line 14) | function _e(e){return(e=e.target||e.srcElement||window).correspondingUse...
function Pe (line 14) | function Pe(e){if(e=ro(e)){if("function"!=typeof ke)throw Error(a(280));...
function Te (line 14) | function Te(e){Oe?Be?Be.push(e):Be=[e]:Oe=e}
function Ie (line 14) | function Ie(){if(Oe){var e=Oe,n=Be;if(Be=Oe=null,Pe(e),n)for(e=0;e<n.len...
function je (line 14) | function je(e,n){return e(n)}
function ze (line 14) | function ze(e,n,t,r,o){return e(n,t,r,o)}
function De (line 14) | function De(){}
function Fe (line 14) | function Fe(){null===Oe&&null===Be||(De(),Ie())}
function Le (line 14) | function Le(e,n){var t=e.stateNode;if(null===t)return null;var r=io(t);i...
function We (line 14) | function We(e,n,t,r,o,i,a,l,c){var s=Array.prototype.slice.call(argument...
function Ke (line 14) | function Ke(e,n,t,r,o,i,a,l,c){Ge=!1,Ye=null,We.apply($e,arguments)}
function Qe (line 14) | function Qe(e){var n=e,t=e;if(e.alternate)for(;n.return;)n=n.return;else...
function Xe (line 14) | function Xe(e){if(13===e.tag){var n=e.memoizedState;if(null===n&&(null!=...
function Ze (line 14) | function Ze(e){if(Qe(e)!==e)throw Error(a(188))}
function Je (line 14) | function Je(e){if(e=function(e){var n=e.alternate;if(!n){if(null===(n=Qe...
function en (line 14) | function en(e,n){for(var t=e.alternate;null!==n;){if(n===e||n===t)return...
function hn (line 14) | function hn(e,n,t,r,o){return{blockedOn:e,domEventName:n,eventSystemFlag...
function An (line 14) | function An(e,n){switch(e){case"focusin":case"focusout":cn=null;break;ca...
function bn (line 14) | function bn(e,n,t,r,o,i){return null===e||e.nativeEvent!==i?(e=hn(n,t,r,...
function vn (line 14) | function vn(e){var n=to(e.target);if(null!==n){var t=Qe(n);if(null!==t)i...
function mn (line 14) | function mn(e){if(null!==e.blockedOn)return!1;for(var n=e.targetContaine...
function yn (line 14) | function yn(e,n,t){mn(e)&&t.delete(n)}
function En (line 14) | function En(){for(an=!1;0<ln.length;){var e=ln[0];if(null!==e.blockedOn)...
function Cn (line 14) | function Cn(e,n){e.blockedOn===n&&(e.blockedOn=null,an||(an=!0,i.unstabl...
function wn (line 14) | function wn(e){function n(n){return Cn(n,e)}if(0<ln.length){Cn(ln[0],e);...
function xn (line 14) | function xn(e,n){var t={};return t[e.toLowerCase()]=n.toLowerCase(),t["W...
function On (line 14) | function On(e){if(_n[e])return _n[e];if(!Sn[e])return e;var n,t=Sn[e];fo...
function Rn (line 14) | function Rn(e,n){for(var t=0;t<e.length;t+=2){var r=e[t],o=e[t+1];o="on"...
function Nn (line 14) | function Nn(e){if(1&e)return Mn=15,1;if(2&e)return Mn=14,2;if(4&e)return...
function Fn (line 14) | function Fn(e,n){var t=e.pendingLanes;if(0===t)return Mn=0;var r=0,o=0,i...
function Ln (line 14) | function Ln(e){return 0!==(e=-1073741825&e.pendingLanes)?e:1073741824&e?...
function Hn (line 14) | function Hn(e,n){switch(e){case 15:return 1;case 14:return 2;case 12:ret...
function Un (line 14) | function Un(e){return e&-e}
function Wn (line 14) | function Wn(e){for(var n=[],t=0;31>t;t++)n.push(e);return n}
function Gn (line 14) | function Gn(e,n,t){e.pendingLanes|=n;var r=n-1;e.suspendedLanes&=r,e.pin...
function Xn (line 14) | function Xn(e,n,t,r){Me||De();var o=Jn,i=Me;Me=!0;try{ze(o,e,n,t,r)}fina...
function Zn (line 14) | function Zn(e,n,t,r){Kn($n,Jn.bind(null,e,n,t,r))}
function Jn (line 14) | function Jn(e,n,t,r){var o;if(Qn)if((o=!(4&n))&&0<ln.length&&-1<gn.index...
function et (line 14) | function et(e,n,t,r){var o=_e(r);if(null!==(o=to(o))){var i=Qe(o);if(nul...
function ot (line 14) | function ot(){if(rt)return rt;var e,n,t=tt,r=t.length,o="value"in nt?nt....
function it (line 14) | function it(e){var n=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&...
function at (line 14) | function at(){return!0}
function lt (line 14) | function lt(){return!1}
function ct (line 14) | function ct(e){function n(n,t,r,o,i){for(var a in this._reactName=n,this...
function kt (line 14) | function kt(e){var n=this.nativeEvent;return n.getModifierState?n.getMod...
function Ot (line 14) | function Ot(){return kt}
function Wt (line 14) | function Wt(e,n){switch(e){case"keyup":return-1!==Rt.indexOf(n.keyCode);...
function Gt (line 14) | function Gt(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}
function Vt (line 14) | function Vt(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return"inpu...
function $t (line 14) | function $t(e,n,t,r){Te(r),0<(n=Mr(n,"onChange")).length&&(t=new dt("onC...
function Xt (line 14) | function Xt(e){Br(e,0)}
function Zt (line 14) | function Zt(e){if(X(oo(e)))return e}
function Jt (line 14) | function Jt(e,n){if("change"===e)return n}
function or (line 14) | function or(){Kt&&(Kt.detachEvent("onpropertychange",ir),Qt=Kt=null)}
function ir (line 14) | function ir(e){if("value"===e.propertyName&&Zt(Qt)){var n=[];if($t(n,Qt,...
function ar (line 14) | function ar(e,n,t){"focusin"===e?(or(),Qt=t,(Kt=n).attachEvent("onproper...
function lr (line 14) | function lr(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)retu...
function cr (line 14) | function cr(e,n){if("click"===e)return Zt(n)}
function sr (line 14) | function sr(e,n){if("input"===e||"change"===e)return Zt(n)}
function fr (line 14) | function fr(e,n){if(ur(e,n))return!0;if("object"!=typeof e||null===e||"o...
function dr (line 14) | function dr(e){for(;e&&e.firstChild;)e=e.firstChild;return e}
function gr (line 14) | function gr(e,n){var t,r=dr(e);for(e=0;r;){if(3===r.nodeType){if(t=e+r.t...
function hr (line 14) | function hr(e,n){return!(!e||!n)&&(e===n||(!e||3!==e.nodeType)&&(n&&3===...
function Ar (line 14) | function Ar(){for(var e=window,n=Z();n instanceof e.HTMLIFrameElement;){...
function br (line 14) | function br(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return n&&(...
function wr (line 14) | function wr(e,n,t){var r=t.window===t?t.document:9===t.nodeType?t:t.owne...
function Or (line 14) | function Or(e,n,t){var r=e.type||"unknown-event";e.currentTarget=t,funct...
function Br (line 14) | function Br(e,n){n=!!(4&n);for(var t=0;t<e.length;t++){var r=e[t],o=r.ev...
function Pr (line 14) | function Pr(e,n){var t=ao(n),r=e+"__bubble";t.has(r)||(zr(n,e,2,!1),t.ad...
function Ir (line 14) | function Ir(e){e[Tr]||(e[Tr]=!0,l.forEach((function(n){kr.has(n)||jr(n,!...
function jr (line 14) | function jr(e,n,t,r){var o=4<arguments.length&&void 0!==arguments[4]?arg...
function zr (line 14) | function zr(e,n,t,r){var o=zn.get(n);switch(void 0===o?2:o){case 0:o=Xn;...
function Dr (line 14) | function Dr(e,n,t,r,o){var i=r;if(!(1&n||2&n||null===r))e:for(;;){if(nul...
function Rr (line 14) | function Rr(e,n,t){return{instance:e,listener:n,currentTarget:t}}
function Mr (line 14) | function Mr(e,n){for(var t=n+"Capture",r=[];null!==e;){var o=e,i=o.state...
function Nr (line 14) | function Nr(e){if(null===e)return null;do{e=e.return}while(e&&5!==e.tag)...
function Fr (line 14) | function Fr(e,n,t,r,o){for(var i=n._reactName,a=[];null!==t&&t!==r;){var...
function Lr (line 14) | function Lr(){}
function Wr (line 14) | function Wr(e,n){switch(e){case"button":case"input":case"select":case"te...
function Gr (line 14) | function Gr(e,n){return"textarea"===e||"option"===e||"noscript"===e||"st...
function Vr (line 14) | function Vr(e){1===e.nodeType?e.textContent="":9===e.nodeType&&(null!=(e...
function $r (line 14) | function $r(e){for(;null!=e;e=e.nextSibling){var n=e.nodeType;if(1===n||...
function Kr (line 14) | function Kr(e){e=e.previousSibling;for(var n=0;e;){if(8===e.nodeType){va...
function to (line 14) | function to(e){var n=e[Zr];if(n)return n;for(var t=e.parentNode;t;){if(n...
function ro (line 14) | function ro(e){return!(e=e[Zr]||e[eo])||5!==e.tag&&6!==e.tag&&13!==e.tag...
function oo (line 14) | function oo(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(a(...
function io (line 14) | function io(e){return e[Jr]||null}
function ao (line 14) | function ao(e){var n=e[no];return void 0===n&&(n=e[no]=new Set),n}
function so (line 14) | function so(e){return{current:e}}
function uo (line 14) | function uo(e){0>co||(e.current=lo[co],lo[co]=null,co--)}
function po (line 14) | function po(e,n){co++,lo[co]=e.current,e.current=n}
function bo (line 14) | function bo(e,n){var t=e.type.contextTypes;if(!t)return fo;var r=e.state...
function vo (line 14) | function vo(e){return null!=(e=e.childContextTypes)}
function mo (line 14) | function mo(){uo(ho),uo(go)}
function yo (line 14) | function yo(e,n,t){if(go.current!==fo)throw Error(a(168));po(go,n),po(ho...
function Eo (line 14) | function Eo(e,n,t){var r=e.stateNode;if(e=n.childContextTypes,"function"...
function Co (line 14) | function Co(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMerged...
function wo (line 14) | function wo(e,n,t){var r=e.stateNode;if(!r)throw Error(a(169));t?(e=Eo(e...
function Yo (line 14) | function Yo(){switch(Io()){case jo:return 99;case zo:return 98;case Do:r...
function qo (line 14) | function qo(e){switch(e){case 99:return jo;case 98:return zo;case 97:ret...
function Vo (line 14) | function Vo(e,n){return e=qo(e),_o(e,n)}
function $o (line 14) | function $o(e,n,t){return e=qo(e),ko(e,n,t)}
function Ko (line 14) | function Ko(){if(null!==Ho){var e=Ho;Ho=null,Oo(e)}Qo()}
function Qo (line 14) | function Qo(){if(!Uo&&null!==Lo){Uo=!0;var e=0;try{var n=Lo;Vo(99,(funct...
function Zo (line 14) | function Zo(e,n){if(e&&e.defaultProps){for(var t in n=o({},n),e=e.defaul...
function ri (line 14) | function ri(){ti=ni=ei=null}
function oi (line 14) | function oi(e){var n=Jo.current;uo(Jo),e.type._context._currentValue=n}
function ii (line 14) | function ii(e,n){for(;null!==e;){var t=e.alternate;if((e.childLanes&n)==...
function ai (line 14) | function ai(e,n){ei=e,ti=ni=null,null!==(e=e.dependencies)&&null!==e.fir...
function li (line 14) | function li(e,n){if(ti!==e&&!1!==n&&0!==n)if("number"==typeof n&&1073741...
function si (line 14) | function si(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:...
function ui (line 14) | function ui(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={base...
function pi (line 14) | function pi(e,n){return{eventTime:e,lane:n,tag:0,payload:null,callback:n...
function fi (line 14) | function fi(e,n){if(null!==(e=e.updateQueue)){var t=(e=e.shared).pending...
function di (line 14) | function di(e,n){var t=e.updateQueue,r=e.alternate;if(null!==r&&t===(r=r...
function gi (line 14) | function gi(e,n,t,r){var i=e.updateQueue;ci=!1;var a=i.firstBaseUpdate,l...
function hi (line 14) | function hi(e,n,t){if(e=n.effects,n.effects=null,null!==e)for(n=0;n<e.le...
function bi (line 14) | function bi(e,n,t,r){t=null==(t=t(r,n=e.memoizedState))?n:o({},n,t),e.me...
function mi (line 14) | function mi(e,n,t,r,o,i,a){return"function"==typeof(e=e.stateNode).shoul...
function yi (line 14) | function yi(e,n,t){var r=!1,o=fo,i=n.contextType;return"object"==typeof ...
function Ei (line 14) | function Ei(e,n,t,r){e=n.state,"function"==typeof n.componentWillReceive...
function Ci (line 14) | function Ci(e,n,t,r){var o=e.stateNode;o.props=t,o.state=e.memoizedState...
function xi (line 14) | function xi(e,n,t){if(null!==(e=t.ref)&&"function"!=typeof e&&"object"!=...
function Si (line 14) | function Si(e,n){if("textarea"!==e.type)throw Error(a(31,"[object Object...
function _i (line 14) | function _i(e){function n(n,t){if(e){var r=n.lastEffect;null!==r?(r.next...
function ji (line 14) | function ji(e){if(e===Bi)throw Error(a(174));return e}
function zi (line 14) | function zi(e,n){switch(po(Ii,n),po(Ti,e),po(Pi,Bi),e=n.nodeType){case 9...
function Di (line 14) | function Di(){uo(Pi),uo(Ti),uo(Ii)}
function Ri (line 14) | function Ri(e){ji(Ii.current);var n=ji(Pi.current),t=ge(n,e.type);n!==t&...
function Mi (line 14) | function Mi(e){Ti.current===e&&(uo(Pi),uo(Ti))}
function Fi (line 14) | function Fi(e){for(var n=e;null!==n;){if(13===n.tag){var t=n.memoizedSta...
function Wi (line 14) | function Wi(e,n){var t=Yc(5,null,null,0);t.elementType="DELETED",t.type=...
function Gi (line 14) | function Gi(e,n){switch(e.tag){case 5:var t=e.type;return null!==(n=1!==...
function Yi (line 14) | function Yi(e){if(Ui){var n=Hi;if(n){var t=n;if(!Gi(e,n)){if(!(n=$r(t.ne...
function qi (line 14) | function qi(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag...
function Vi (line 14) | function Vi(e){if(e!==Li)return!1;if(!Ui)return qi(e),Ui=!0,!1;var n=e.t...
function $i (line 14) | function $i(){Hi=Li=null,Ui=!1}
function Qi (line 14) | function Qi(){for(var e=0;e<Ki.length;e++)Ki[e]._workInProgressVersionPr...
function ia (line 14) | function ia(){throw Error(a(321))}
function aa (line 14) | function aa(e,n){if(null===n)return!1;for(var t=0;t<n.length&&t<e.length...
function la (line 14) | function la(e,n,t,r,o,i){if(Ji=i,ea=n,n.memoizedState=null,n.updateQueue...
function ca (line 14) | function ca(){var e={memoizedState:null,baseState:null,baseQueue:null,qu...
function sa (line 14) | function sa(){if(null===na){var e=ea.alternate;e=null!==e?e.memoizedStat...
function ua (line 14) | function ua(e,n){return"function"==typeof n?n(e):n}
function pa (line 14) | function pa(e){var n=sa(),t=n.queue;if(null===t)throw Error(a(311));t.la...
function fa (line 14) | function fa(e){var n=sa(),t=n.queue;if(null===t)throw Error(a(311));t.la...
function da (line 14) | function da(e,n,t){var r=n._getVersion;r=r(n._source);var o=n._workInPro...
function ga (line 14) | function ga(e,n,t,r){var o=zl;if(null===o)throw Error(a(349));var i=n._g...
function ha (line 14) | function ha(e,n,t){return ga(sa(),e,n,t)}
function Aa (line 14) | function Aa(e){var n=ca();return"function"==typeof e&&(e=e()),n.memoized...
function ba (line 14) | function ba(e,n,t,r){return e={tag:e,create:n,destroy:t,deps:r,next:null...
function va (line 14) | function va(e){return e={current:e},ca().memoizedState=e}
function ma (line 14) | function ma(){return sa().memoizedState}
function ya (line 14) | function ya(e,n,t,r){var o=ca();ea.flags|=e,o.memoizedState=ba(1|n,t,voi...
function Ea (line 14) | function Ea(e,n,t,r){var o=sa();r=void 0===r?null:r;var i=void 0;if(null...
function Ca (line 14) | function Ca(e,n){return ya(516,4,e,n)}
function wa (line 14) | function wa(e,n){return Ea(516,4,e,n)}
function xa (line 14) | function xa(e,n){return Ea(4,2,e,n)}
function Sa (line 14) | function Sa(e,n){return"function"==typeof n?(e=e(),n(e),function(){n(nul...
function _a (line 14) | function _a(e,n,t){return t=null!=t?t.concat([e]):null,Ea(4,2,Sa.bind(nu...
function ka (line 14) | function ka(){}
function Oa (line 14) | function Oa(e,n){var t=sa();n=void 0===n?null:n;var r=t.memoizedState;re...
function Ba (line 14) | function Ba(e,n){var t=sa();n=void 0===n?null:n;var r=t.memoizedState;re...
function Pa (line 14) | function Pa(e,n){var t=Yo();Vo(98>t?98:t,(function(){e(!0)})),Vo(97<t?97...
function Ta (line 14) | function Ta(e,n,t){var r=dc(),o=gc(e),i={lane:o,action:t,eagerReducer:nu...
function Na (line 14) | function Na(e,n,t,r){n.child=null===e?Oi(n,null,t,r):ki(n,e.child,t,r)}
function Fa (line 14) | function Fa(e,n,t,r,o){t=t.render;var i=n.ref;return ai(n,o),r=la(e,n,t,...
function La (line 14) | function La(e,n,t,r,o,i){if(null===e){var a=t.type;return"function"!=typ...
function Ha (line 14) | function Ha(e,n,t,r,o,i){if(null!==e&&fr(e.memoizedProps,r)&&e.ref===n.r...
function Ua (line 14) | function Ua(e,n,t){var r=n.pendingProps,o=r.children,i=null!==e?e.memoiz...
function Wa (line 14) | function Wa(e,n){var t=n.ref;(null===e&&null!==t||null!==e&&e.ref!==t)&&...
function Ga (line 14) | function Ga(e,n,t,r,o){var i=vo(t)?Ao:go.current;return i=bo(n,i),ai(n,o...
function Ya (line 14) | function Ya(e,n,t,r,o){if(vo(t)){var i=!0;Co(n)}else i=!1;if(ai(n,o),nul...
function qa (line 14) | function qa(e,n,t,r,o,i){Wa(e,n);var a=!!(64&n.flags);if(!r&&!a)return o...
function Va (line 14) | function Va(e){var n=e.stateNode;n.pendingContext?yo(0,n.pendingContext,...
function Ja (line 14) | function Ja(e,n,t){var r,o=n.pendingProps,i=Ni.current,a=!1;return(r=!!(...
function el (line 14) | function el(e,n,t,r){var o=e.mode,i=e.child;return n={mode:"hidden",chil...
function nl (line 14) | function nl(e,n,t,r){var o=e.child;return e=o.sibling,t=Vc(o,{mode:"visi...
function tl (line 14) | function tl(e,n,t,r,o){var i=n.mode,a=e.child;e=a.sibling;var l={mode:"h...
function rl (line 14) | function rl(e,n){e.lanes|=n;var t=e.alternate;null!==t&&(t.lanes|=n),ii(...
function ol (line 14) | function ol(e,n,t,r,o,i){var a=e.memoizedState;null===a?e.memoizedState=...
function il (line 14) | function il(e,n,t){var r=n.pendingProps,o=r.revealOrder,i=r.tail;if(Na(e...
function al (line 14) | function al(e,n,t){if(null!==e&&(n.dependencies=e.dependencies),Ul|=n.la...
function ll (line 14) | function ll(e,n){if(!Ui)switch(e.tailMode){case"hidden":n=e.tail;for(var...
function cl (line 14) | function cl(e,n,t){var r=n.pendingProps;switch(n.tag){case 2:case 16:cas...
function sl (line 14) | function sl(e){switch(e.tag){case 1:vo(e.type)&&mo();var n=e.flags;retur...
function ul (line 14) | function ul(e,n){try{var t="",r=n;do{t+=q(r),r=r.return}while(r);var o=t...
function pl (line 14) | function pl(e,n){try{console.error(n.value)}catch(e){setTimeout((functio...
function dl (line 14) | function dl(e,n,t){(t=pi(-1,t)).tag=3,t.payload={element:null};var r=n.v...
function gl (line 14) | function gl(e,n,t){(t=pi(-1,t)).tag=3;var r=e.type.getDerivedStateFromEr...
function Al (line 14) | function Al(e){var n=e.ref;if(null!==n)if("function"==typeof n)try{n(nul...
function bl (line 14) | function bl(e,n){switch(n.tag){case 0:case 11:case 15:case 22:case 5:cas...
function vl (line 14) | function vl(e,n,t){switch(t.tag){case 0:case 11:case 15:case 22:if(null!...
function ml (line 14) | function ml(e,n){for(var t=e;;){if(5===t.tag){var r=t.stateNode;if(n)"fu...
function yl (line 14) | function yl(e,n){if(So&&"function"==typeof So.onCommitFiberUnmount)try{S...
function El (line 14) | function El(e){e.alternate=null,e.child=null,e.dependencies=null,e.first...
function Cl (line 14) | function Cl(e){return 5===e.tag||3===e.tag||4===e.tag}
function wl (line 14) | function wl(e){e:{for(var n=e.return;null!==n;){if(Cl(n))break e;n=n.ret...
function xl (line 14) | function xl(e,n,t){var r=e.tag,o=5===r||6===r;if(o)e=o?e.stateNode:e.sta...
function Sl (line 14) | function Sl(e,n,t){var r=e.tag,o=5===r||6===r;if(o)e=o?e.stateNode:e.sta...
function _l (line 14) | function _l(e,n){for(var t,r,o=n,i=!1;;){if(!i){i=o.return;e:for(;;){if(...
function kl (line 14) | function kl(e,n){switch(n.tag){case 0:case 11:case 14:case 15:case 22:va...
function Ol (line 14) | function Ol(e){var n=e.updateQueue;if(null!==n){e.updateQueue=null;var t...
function Bl (line 14) | function Bl(e,n){return null!==e&&(null===(e=e.memoizedState)||null!==e....
function $l (line 14) | function $l(){Vl=Go()+500}
function dc (line 14) | function dc(){return 48&jl?Go():-1!==cc?cc:cc=Go()}
function gc (line 14) | function gc(e){if(!(2&(e=e.mode)))return 1;if(!(4&e))return 99===Yo()?1:...
function hc (line 14) | function hc(e,n,t){if(50<ac)throw ac=0,lc=null,Error(a(185));if(null===(...
function Ac (line 14) | function Ac(e,n){e.lanes|=n;var t=e.alternate;for(null!==t&&(t.lanes|=n)...
function bc (line 14) | function bc(e,n){for(var t=e.callbackNode,r=e.suspendedLanes,o=e.pingedL...
function vc (line 14) | function vc(e){if(cc=-1,uc=sc=0,48&jl)throw Error(a(327));var n=e.callba...
function mc (line 14) | function mc(e,n){for(n&=~Gl,n&=~Wl,e.suspendedLanes|=n,e.pingedLanes&=~n...
function yc (line 14) | function yc(e){if(48&jl)throw Error(a(327));if(Rc(),e===zl&&0!==(e.expir...
function Ec (line 14) | function Ec(e,n){var t=jl;jl|=1;try{return e(n)}finally{0===(jl=t)&&($l(...
function Cc (line 14) | function Cc(e,n){var t=jl;jl&=-2,jl|=8;try{return e(n)}finally{0===(jl=t...
function wc (line 14) | function wc(e,n){po(Nl,Ml),Ml|=n,Hl|=n}
function xc (line 14) | function xc(){Ml=Nl.current,uo(Nl)}
function Sc (line 14) | function Sc(e,n){e.finishedWork=null,e.finishedLanes=0;var t=e.timeoutHa...
function _c (line 14) | function _c(e,n){for(;;){var t=Dl;try{if(ri(),Xi.current=Ia,ra){for(var ...
function kc (line 14) | function kc(){var e=Tl.current;return Tl.current=Ia,null===e?Ia:e}
function Oc (line 14) | function Oc(e,n){var t=jl;jl|=16;var r=kc();for(zl===e&&Rl===n||Sc(e,n);...
function Bc (line 14) | function Bc(){for(;null!==Dl;)Tc(Dl)}
function Pc (line 14) | function Pc(){for(;null!==Dl&&!Bo();)Tc(Dl)}
function Tc (line 14) | function Tc(e){var n=Kl(e.alternate,e,Ml);e.memoizedProps=e.pendingProps...
function Ic (line 14) | function Ic(e){var n=e;do{var t=n.alternate;if(e=n.return,2048&n.flags){...
function jc (line 14) | function jc(e){var n=Yo();return Vo(99,zc.bind(null,e,n)),null}
function zc (line 14) | function zc(e,n){do{Rc()}while(null!==nc);if(48&jl)throw Error(a(327));v...
function Dc (line 14) | function Dc(){for(;null!==Ql;){var e=Ql.alternate;fc||null===pc||(8&Ql.f...
function Rc (line 14) | function Rc(){if(90!==tc){var e=97<tc?97:tc;return tc=90,Vo(e,Fc)}return!1}
function Mc (line 14) | function Mc(e,n){rc.push(n,e),ec||(ec=!0,$o(97,(function(){return Rc(),n...
function Nc (line 14) | function Nc(e,n){oc.push(n,e),ec||(ec=!0,$o(97,(function(){return Rc(),n...
function Fc (line 14) | function Fc(){if(null===nc)return!1;var e=nc;if(nc=null,48&jl)throw Erro...
function Lc (line 14) | function Lc(e,n,t){fi(e,n=dl(0,n=ul(t,n),1)),n=dc(),null!==(e=Ac(e,1))&&...
function Hc (line 14) | function Hc(e,n){if(3===e.tag)Lc(e,e,n);else for(var t=e.return;null!==t...
function Uc (line 14) | function Uc(e,n,t){var r=e.pingCache;null!==r&&r.delete(n),n=dc(),e.ping...
function Wc (line 14) | function Wc(e,n){var t=e.stateNode;null!==t&&t.delete(n),0===(n=0)&&(2&(...
function Gc (line 14) | function Gc(e,n,t,r){this.tag=e,this.key=t,this.sibling=this.child=this....
function Yc (line 14) | function Yc(e,n,t,r){return new Gc(e,n,t,r)}
function qc (line 14) | function qc(e){return!(!(e=e.prototype)||!e.isReactComponent)}
function Vc (line 14) | function Vc(e,n){var t=e.alternate;return null===t?((t=Yc(e.tag,n,e.key,...
function $c (line 14) | function $c(e,n,t,r,o,i){var l=2;if(r=e,"function"==typeof e)qc(e)&&(l=1...
function Kc (line 14) | function Kc(e,n,t,r){return(e=Yc(7,e,r,n)).lanes=t,e}
function Qc (line 14) | function Qc(e,n,t,r){return(e=Yc(23,e,r,n)).elementType=M,e.lanes=t,e}
function Xc (line 14) | function Xc(e,n,t){return(e=Yc(6,e,null,n)).lanes=t,e}
function Zc (line 14) | function Zc(e,n,t){return(n=Yc(4,null!==e.children?e.children:[],e.key,n...
function Jc (line 14) | function Jc(e,n,t){this.tag=n,this.containerInfo=e,this.finishedWork=thi...
function es (line 14) | function es(e,n,t,r){var o=n.current,i=dc(),l=gc(o);e:if(t){n:{if(Qe(t=t...
function ns (line 14) | function ns(e){return(e=e.current).child?(e.child.tag,e.child.stateNode)...
function ts (line 14) | function ts(e,n){if(null!==(e=e.memoizedState)&&null!==e.dehydrated){var...
function rs (line 14) | function rs(e,n){ts(e,n),(e=e.alternate)&&ts(e,n)}
function os (line 14) | function os(e,n,t){var r=null!=t&&null!=t.hydrationOptions&&t.hydrationO...
function is (line 14) | function is(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeTy...
function as (line 14) | function as(e,n,t,r,o){var i=t._reactRootContainer;if(i){var a=i._intern...
function ls (line 14) | function ls(e,n){var t=2<arguments.length&&void 0!==arguments[2]?argumen...
function s (line 23) | function s(e,n,t){var r,i={},s=null,u=null;for(r in void 0!==t&&(s=""+t)...
function d (line 31) | function d(e){for(var n="https://reactjs.org/docs/error-decoder.html?inv...
function A (line 31) | function A(e,n,t){this.props=e,this.context=n,this.refs=h,this.updater=t...
function b (line 31) | function b(){}
function v (line 31) | function v(e,n,t){this.props=e,this.context=n,this.refs=h,this.updater=t...
function w (line 31) | function w(e,n,t){var r,i={},a=null,l=null;if(null!=n)for(r in void 0!==...
function x (line 31) | function x(e){return"object"==typeof e&&null!==e&&e.$$typeof===o}
function _ (line 31) | function _(e,n){return"object"==typeof e&&null!==e&&null!=e.key?function...
function k (line 31) | function k(e,n,t,r,a){var l=typeof e;"undefined"!==l&&"boolean"!==l||(e=...
function O (line 31) | function O(e,n,t){if(null==e)return e;var r=[],o=0;return k(e,r,"","",(f...
function B (line 31) | function B(e){if(-1===e._status){var n=e._result;n=n(),e._status=0,e._re...
function T (line 31) | function T(){var e=P.current;if(null===e)throw Error(d(321));return e}
function C (line 39) | function C(e,n){var t=e.length;e.push(n);e:for(;;){var r=t-1>>>1,o=e[r];...
function w (line 39) | function w(e){return void 0===(e=e[0])?null:e}
function x (line 39) | function x(e){var n=e[0];if(void 0!==n){var t=e.pop();if(t!==n){e[0]=t;e...
function S (line 39) | function S(e,n){var t=e.sortIndex-n.sortIndex;return 0!==t?t:e.id-n.id}
function z (line 39) | function z(e){for(var n=w(k);null!==n;){if(null===n.callback)x(k);else{i...
function D (line 39) | function D(e){if(j=!1,z(e),!I)if(null!==w(_))I=!0,t(R);else{var n=w(k);n...
function R (line 39) | function R(e,t){I=!1,j&&(j=!1,o()),T=!0;var i=P;try{for(z(t),B=w(_);null...
function p (line 39) | function p(e,n){var t=i({},u);return e.forEach((function(e){var r=e[0],o...
function e (line 39) | function e(){this.os=u,this.device=u,this.browser=u}
function r (line 39) | function r(e){try{if(!t.g.localStorage)return!1}catch(e){return!1}var n=...
function o (line 44) | function o(){for(var e="",n=0;n<arguments.length;n++){var t=arguments[n]...
function i (line 44) | function i(e){if("string"==typeof e||"number"==typeof e)return e;if("obj...
function a (line 44) | function a(e,n){return n?e?e+" "+n:e+n:e}
function e (line 44) | function e(){}
function e (line 44) | function e(){}
function t (line 44) | function t(r){var o=n[r];if(void 0!==o)return o.exports;var i=n[r]={id:r...
function c (line 44) | function c(){return c=Object.assign?Object.assign.bind():function(e){for...
function s (line 44) | function s(e,n){if(!(e instanceof n))throw new TypeError("Cannot call a ...
function u (line 44) | function u(e){return u="function"==typeof Symbol&&"symbol"==typeof Symbo...
function p (line 44) | function p(e){var n=function(e,n){if("object"!=u(e)||!e)return e;var t=e...
function f (line 44) | function f(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.en...
function d (line 44) | function d(e,n,t){return n&&f(e.prototype,n),t&&f(e,t),Object.defineProp...
function g (line 44) | function g(e,n){if(n&&("object"==u(n)||"function"==typeof n))return n;if...
function h (line 44) | function h(e){return h=Object.setPrototypeOf?Object.getPrototypeOf.bind(...
function A (line 44) | function A(e,n){return A=Object.setPrototypeOf?Object.setPrototypeOf.bin...
function b (line 44) | function b(e,n){if("function"!=typeof n&&null!==n)throw new TypeError("S...
function v (line 44) | function v(e,n,t){return(n=p(n))in e?Object.defineProperty(e,n,{value:t,...
function m (line 44) | function m(e){var n,t,r="";if("string"==typeof e||"number"==typeof e)r+=...
function E (line 44) | function E(e){var n=e.cellCount,t=e.cellSize,r=e.computeMetadataCallback...
function _ (line 44) | function _(){var e=!(arguments.length>0&&void 0!==arguments[0])||argumen...
function k (line 44) | function k(e){var n=e.cellSize,t=e.cellSizeAndPositionManager,r=e.previo...
function T (line 44) | function T(){var e=this.constructor.getDerivedStateFromProps(this.props,...
function I (line 44) | function I(e){this.setState(function(n){var t=this.constructor.getDerive...
function j (line 44) | function j(e,n){try{var t=this.props,r=this.state;this.props=e,this.stat...
function L (line 44) | function L(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){va...
function H (line 44) | function H(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[...
function U (line 44) | function U(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct...
function t (line 44) | function t(e){var n,r,o,i;s(this,t),r=this,i=[e],o=h(o=t),v(n=g(r,U()?Re...
function V (line 44) | function V(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct...
function t (line 44) | function t(){var e,n,r,o;s(this,t);for(var i=arguments.length,a=new Arra...
function K (line 44) | function K(e,n){var r,o=void 0!==(r=void 0!==n?n:"undefined"!=typeof win...
function Q (line 44) | function Q(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){va...
function X (line 44) | function X(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[...
function Z (line 44) | function Z(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construct...
function t (line 44) | function t(){var e,n,r,o;s(this,t);for(var i=arguments.length,a=new Arra...
function re (line 44) | function re(){te&&(te=null,document.body&&null!=ne&&(document.body.style...
function oe (line 44) | function oe(){re(),ee.forEach((function(e){return e.__resetIsScrolling()...
function ie (line 44) | function ie(e){e.currentTarget===window&&null==ne&&document.body&&(ne=do...
function ae (line 44) | function ae(e,n){ee.some((function(e){return e.props.scrollElement===n})...
function le (line 44) | function le(e,n){(ee=ee.filter((function(n){return n!==e}))).length||(n....
function ue (line 44) | function ue(e,n){if(e){if(ce(e)){var t=window,r=t.innerHeight,o=t.innerW...
function pe (line 44) | function pe(e){return ce(e)&&document.documentElement?{top:"scrollY"in w...
function fe (line 44) | function fe(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function de (line 44) | function de(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function ge (line 44) | function ge(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function t (line 44) | function t(){var e,r,o,i;s(this,t);for(var a=arguments.length,l=new Arra...
function ve (line 44) | function ve(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function t (line 44) | function t(){var e,r,o,i;s(this,t);for(var a=arguments.length,l=new Arra...
function Ee (line 44) | function Ee(e,n){return"".concat(e,"-").concat(n)}
function Se (line 44) | function Se(e,n){if(e.length!==n.length)return!1;for(var t=0;t<e.length;...
function _e (line 44) | function _e(e,n){void 0===n&&(n=Se);var t=null;function r(){for(var r=[]...
function ke (line 44) | function ke(e){return ke="function"==typeof Symbol&&"symbol"==typeof Sym...
function Oe (line 44) | function Oe(e){return function(e){if(Array.isArray(e))return Te(e)}(e)||...
function Be (line 44) | function Be(e,n){return function(e){if(Array.isArray(e))return e}(e)||fu...
function Pe (line 44) | function Pe(e,n){if(e){if("string"==typeof e)return Te(e,n);var t={}.toS...
function Te (line 44) | function Te(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=Array...
function Ie (line 44) | function Ie(){for(var e=arguments.length,n=new Array(e),t=0;t<e;t++)n[t]...
function De (line 44) | function De(e){if(!e)throw Error('Argument "name" is required in getUID(...
function Re (line 44) | function Re(e){var n,t,r=!1;function o(){t=window.requestAnimationFrame(...
function Me (line 44) | function Me(e){var n=new Map,t=new WeakMap;return function(r){var o=null...
function Ne (line 44) | function Ne(e){return Ne="function"==typeof Symbol&&"symbol"==typeof Sym...
function Fe (line 44) | function Fe(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Le (line 44) | function Le(e){var n=function(e,n){if("object"!=Ne(e)||!e)return e;var t...
function Ue (line 44) | function Ue(e){return e===document||e instanceof Node&&document.document...
function Ge (line 44) | function Ge(e){if(e instanceof Range||null!=e&&Ue(e)){var n=e.getBoundin...
function Ye (line 44) | function Ye(){return window.innerHeight}
function qe (line 44) | function qe(e){var n=Ge(e),t=n.top,r=n.bottom,o=n.left,i=n.right;return!...
function en (line 44) | function en(e){return en="function"==typeof Symbol&&"symbol"==typeof Sym...
function nn (line 44) | function nn(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function tn (line 44) | function tn(e,n,t){return(n=rn(n))in e?Object.defineProperty(e,n,{value:...
function rn (line 44) | function rn(e){var n=function(e,n){if("object"!=en(e)||!e)return e;var t...
function ln (line 44) | function ln(e){return ln="function"==typeof Symbol&&"symbol"==typeof Sym...
function cn (line 44) | function cn(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function sn (line 44) | function sn(e,n,t){return n=pn(n),function(e,n){if(n&&("object"==ln(n)||...
function un (line 44) | function un(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function pn (line 44) | function pn(e){return pn=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function fn (line 44) | function fn(e,n){return fn=Object.setPrototypeOf?Object.setPrototypeOf.b...
function dn (line 44) | function dn(e,n,t){return(n=gn(n))in e?Object.defineProperty(e,n,{value:...
function gn (line 44) | function gn(e){var n=function(e,n){if("object"!=ln(e)||!e)return e;var t...
function n (line 44) | function n(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function An (line 44) | function An(e,n){return function(e){if(Array.isArray(e))return e}(e)||fu...
function bn (line 44) | function bn(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=Array...
function mn (line 44) | function mn(e){return mn="function"==typeof Symbol&&"symbol"==typeof Sym...
function yn (line 44) | function yn(){return yn=Object.assign?Object.assign.bind():function(e){f...
function En (line 44) | function En(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Cn (line 44) | function Cn(e,n,t){return n=xn(n),function(e,n){if(n&&("object"==mn(n)||...
function wn (line 44) | function wn(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function xn (line 44) | function xn(e){return xn=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Sn (line 44) | function Sn(e,n){return Sn=Object.setPrototypeOf?Object.setPrototypeOf.b...
function _n (line 44) | function _n(e,n,t){return(n=kn(n))in e?Object.defineProperty(e,n,{value:...
function kn (line 44) | function kn(e){var n=function(e,n){if("object"!=mn(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function In (line 44) | function In(e){return In="function"==typeof Symbol&&"symbol"==typeof Sym...
function jn (line 44) | function jn(){return jn=Object.assign?Object.assign.bind():function(e){f...
function zn (line 44) | function zn(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function Dn (line 44) | function Dn(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function Rn (line 44) | function Rn(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Mn (line 44) | function Mn(e,n,t){return n=Fn(n),function(e,n){if(n&&("object"==In(n)||...
function Nn (line 44) | function Nn(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Fn (line 44) | function Fn(e){return Fn=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Ln (line 44) | function Ln(e,n){return Ln=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Hn (line 44) | function Hn(e,n,t){return(n=Un(n))in e?Object.defineProperty(e,n,{value:...
function Un (line 44) | function Un(e){var n=function(e,n){if("object"!=In(e)||!e)return e;var t...
function t (line 44) | function t(t){var r=t.className,o=t.children,i=l()(Pn().inner,r,e);retur...
function Yn (line 44) | function Yn(e){var t,r="string"!=typeof e&&e!==On;return t=function(t){f...
function Kn (line 44) | function Kn(e){return Kn="function"==typeof Symbol&&"symbol"==typeof Sym...
function Xn (line 44) | function Xn(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function Zn (line 44) | function Zn(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function Jn (line 44) | function Jn(){return Jn=Object.assign?Object.assign.bind():function(e){f...
function et (line 44) | function et(e,n,t){return(n=tt(n))in e?Object.defineProperty(e,n,{value:...
function nt (line 44) | function nt(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function tt (line 44) | function tt(e){var n=function(e,n){if("object"!=Kn(e)||!e)return e;var t...
function rt (line 44) | function rt(e,n,t){return n=it(n),function(e,n){if(n&&("object"==Kn(n)||...
function ot (line 44) | function ot(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function it (line 44) | function it(e){return it=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function at (line 44) | function at(e,n){return at=Object.setPrototypeOf?Object.setPrototypeOf.b...
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function ct (line 44) | function ct(e){if(null==e)return{};var n,t=/([^&;=]+)=?([^&;]*)/g,r={};f...
function st (line 44) | function st(e){return encodeURIComponent(String(e)).replace(/%2C/g,",")}
function ut (line 44) | function ut(e,n){var t,r=-1===e.indexOf("?")?"?":"&",o=e,i=0;for(t in n)...
function pt (line 44) | function pt(e){return 0===e.indexOf("data:")}
function gt (line 44) | function gt(e){return function(e){if(Array.isArray(e))return bt(e)}(e)||...
function ht (line 44) | function ht(e,n){return function(e){if(Array.isArray(e))return e}(e)||fu...
function At (line 44) | function At(e,n){if(e){if("string"==typeof e)return bt(e,n);var t={}.toS...
function bt (line 44) | function bt(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=Array...
function Et (line 44) | function Et(e){var t,r=e.username,o=e.size,i=e.round,a=Math.abs(function...
function xt (line 44) | function xt(e){return xt="function"==typeof Symbol&&"symbol"==typeof Sym...
function St (line 44) | function St(e,n){return function(e){if(Array.isArray(e))return e}(e)||fu...
function _t (line 44) | function _t(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=Array...
function kt (line 44) | function kt(){return kt=Object.assign?Object.assign.bind():function(e){f...
function Ot (line 44) | function Ot(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function Bt (line 44) | function Bt(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function Pt (line 44) | function Pt(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Tt (line 44) | function Tt(e,n,t){return n=jt(n),function(e,n){if(n&&("object"==xt(n)||...
function It (line 44) | function It(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function jt (line 44) | function jt(e){return jt=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function zt (line 44) | function zt(e,n){return zt=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Dt (line 44) | function Dt(e,n,t){return(n=Rt(n))in e?Object.defineProperty(e,n,{value:...
function Rt (line 44) | function Rt(e){var n=function(e,n){if("object"!=xt(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function qt (line 44) | function qt(e){return qt="function"==typeof Symbol&&"symbol"==typeof Sym...
function $t (line 44) | function $t(){return $t=Object.assign?Object.assign.bind():function(e){f...
function Kt (line 44) | function Kt(e,n,t){return(n=function(e){var n=function(e,n){if("object"!...
function Xt (line 44) | function Xt(e){var t,r,o,i=e.src,a=e.className,c=function(e,n){if(null==...
function er (line 44) | function er(e){return er="function"==typeof Symbol&&"symbol"==typeof Sym...
function nr (line 44) | function nr(){return nr=Object.assign?Object.assign.bind():function(e){f...
function tr (line 44) | function tr(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function rr (line 44) | function rr(e,n,t){return n=ir(n),function(e,n){if(n&&("object"==er(n)||...
function or (line 44) | function or(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function ir (line 44) | function ir(e){return ir=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function ar (line 44) | function ar(e,n){return ar=Object.setPrototypeOf?Object.setPrototypeOf.b...
function lr (line 44) | function lr(e,n,t){return(n=cr(n))in e?Object.defineProperty(e,n,{value:...
function cr (line 44) | function cr(e){var n=function(e,n){if("object"!=er(e)||!e)return e;var t...
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function Ar (line 44) | function Ar(e){return Ar="function"==typeof Symbol&&"symbol"==typeof Sym...
function br (line 44) | function br(){return br=Object.assign?Object.assign.bind():function(e){f...
function vr (line 44) | function vr(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function mr (line 44) | function mr(e,n,t){return n=Er(n),function(e,n){if(n&&("object"==Ar(n)||...
function yr (line 44) | function yr(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Er (line 44) | function Er(e){return Er=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Cr (line 44) | function Cr(e,n){return Cr=Object.setPrototypeOf?Object.setPrototypeOf.b...
function wr (line 44) | function wr(e,n,t){return(n=xr(n))in e?Object.defineProperty(e,n,{value:...
function xr (line 44) | function xr(e){var n=function(e,n){if("object"!=Ar(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Br (line 44) | function Br(e){return Br="function"==typeof Symbol&&"symbol"==typeof Sym...
function Pr (line 44) | function Pr(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Tr (line 44) | function Tr(e,n,t){return n=jr(n),function(e,n){if(n&&("object"==Br(n)||...
function Ir (line 44) | function Ir(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function jr (line 44) | function jr(e){return jr=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function zr (line 44) | function zr(e,n){return zr=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Dr (line 44) | function Dr(e,n,t){return(n=Rr(n))in e?Object.defineProperty(e,n,{value:...
function Rr (line 44) | function Rr(e){var n=function(e,n){if("object"!=Br(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Hr (line 44) | function Hr(e){return Hr="function"==typeof Symbol&&"symbol"==typeof Sym...
function Ur (line 44) | function Ur(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Wr (line 44) | function Wr(e,n,t){return n=Yr(n),function(e,n){if(n&&("object"==Hr(n)||...
function Gr (line 44) | function Gr(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Yr (line 44) | function Yr(e){return Yr=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function qr (line 44) | function qr(e,n){return qr=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Vr (line 44) | function Vr(e,n,t){return(n=$r(n))in e?Object.defineProperty(e,n,{value:...
function $r (line 44) | function $r(e){var n=function(e,n){if("object"!=Hr(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Qr (line 44) | function Qr(e){return Qr="function"==typeof Symbol&&"symbol"==typeof Sym...
function Xr (line 44) | function Xr(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Zr (line 44) | function Zr(e){var n=function(e,n){if("object"!=Qr(e)||!e)return e;var t...
function Jr (line 44) | function Jr(e,n,t){return n=no(n),function(e,n){if(n&&("object"==Qr(n)||...
function eo (line 44) | function eo(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function no (line 44) | function no(e){return no=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function to (line 44) | function to(e,n){return to=Object.setPrototypeOf?Object.setPrototypeOf.b...
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function oo (line 44) | function oo(e){return oo="function"==typeof Symbol&&"symbol"==typeof Sym...
function io (line 44) | function io(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function ao (line 44) | function ao(e){var n=function(e,n){if("object"!=oo(e)||!e)return e;var t...
function lo (line 44) | function lo(e,n,t){return n=so(n),function(e,n){if(n&&("object"==oo(n)||...
function co (line 44) | function co(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function so (line 44) | function so(e){return so=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function uo (line 44) | function uo(e,n){return uo=Object.setPrototypeOf?Object.setPrototypeOf.b...
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function fo (line 44) | function fo(e){return fo="function"==typeof Symbol&&"symbol"==typeof Sym...
function go (line 44) | function go(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function ho (line 44) | function ho(e,n,t){return n=bo(n),function(e,n){if(n&&("object"==fo(n)||...
function Ao (line 44) | function Ao(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function bo (line 44) | function bo(e){return bo=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function vo (line 44) | function vo(e,n){return vo=Object.setPrototypeOf?Object.setPrototypeOf.b...
function mo (line 44) | function mo(e){var n=function(e,n){if("object"!=fo(e)||!e)return e;var t...
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function xo (line 44) | function xo(e){return xo="function"==typeof Symbol&&"symbol"==typeof Sym...
function _o (line 44) | function _o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function ko (line 44) | function ko(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function Oo (line 44) | function Oo(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Bo (line 44) | function Bo(e,n,t){return n=To(n),function(e,n){if(n&&("object"==xo(n)||...
function Po (line 44) | function Po(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function To (line 44) | function To(e){return To=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Io (line 44) | function Io(e,n){return Io=Object.setPrototypeOf?Object.setPrototypeOf.b...
function jo (line 44) | function jo(e,n,t){return(n=zo(n))in e?Object.defineProperty(e,n,{value:...
function zo (line 44) | function zo(e){var n=function(e,n){if("object"!=xo(e)||!e)return e;var t...
function Do (line 44) | function Do(){}
function No (line 44) | function No(e){return null!=e&&!Mo.includes(e.rgItemType)&&!e.disabled}
function Fo (line 44) | function Fo(e){return e.map((function(e){return"".concat(e.key,"-").conc...
function a (line 44) | function a(t){var i=t.children,a=An((0,n.useState)(e),2),l=a[0],c=a[1];r...
function l (line 44) | function l(e,t){var r=(0,n.useContext)(o);(0,n.useEffect)((function(){t|...
function c (line 44) | function c(e){return l(e.value,e.skipUpdate),null}
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function ni (line 44) | function ni(e){return ni="function"==typeof Symbol&&"symbol"==typeof Sym...
function ti (line 44) | function ti(e,n,t){return(n=function(e){var n=function(e,n){if("object"!...
function oi (line 44) | function oi(e){return oi="function"==typeof Symbol&&"symbol"==typeof Sym...
function ii (line 44) | function ii(){return ii=Object.assign?Object.assign.bind():function(e){f...
function ai (line 44) | function ai(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function li (line 44) | function li(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function ci (line 44) | function ci(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function si (line 44) | function si(e,n,t){return n=pi(n),function(e,n){if(n&&("object"==oi(n)||...
function ui (line 44) | function ui(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function pi (line 44) | function pi(e){return pi=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function fi (line 44) | function fi(e,n){return fi=Object.setPrototypeOf?Object.setPrototypeOf.b...
function di (line 44) | function di(e,n,t){return(n=gi(n))in e?Object.defineProperty(e,n,{value:...
function gi (line 44) | function gi(e){var n=function(e,n){if("object"!=oi(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function yi (line 44) | function yi(){return yi=Object.assign?Object.assign.bind():function(e){f...
function _i (line 44) | function _i(e){return _i="function"==typeof Symbol&&"symbol"==typeof Sym...
function ki (line 44) | function ki(){return ki=Object.assign?Object.assign.bind():function(e){f...
function Oi (line 44) | function Oi(e){return function(e){if(Array.isArray(e))return Bi(e)}(e)||...
function Bi (line 44) | function Bi(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=Array...
function Pi (line 44) | function Pi(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Ti (line 44) | function Ti(e,n,t){return n=ji(n),function(e,n){if(n&&("object"==_i(n)||...
function Ii (line 44) | function Ii(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function ji (line 44) | function ji(e){return ji=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function zi (line 44) | function zi(e,n){return zi=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Di (line 44) | function Di(e,n,t){return(n=Ri(n))in e?Object.defineProperty(e,n,{value:...
function Ri (line 44) | function Ri(e){var n=function(e,n){if("object"!=_i(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Gi (line 44) | function Gi(e){return Gi="function"==typeof Symbol&&"symbol"==typeof Sym...
function Yi (line 44) | function Yi(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function qi (line 44) | function qi(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function Vi (line 44) | function Vi(e,n,t){return(n=function(e){var n=function(e,n){if("object"!...
function $i (line 44) | function $i(e,n,t){var r=null!==t.container?t.container.clientHeight:Ye(...
function Ki (line 44) | function Ki(e,n,t){var r=null!==t.container?t.container.clientWidth:wind...
function Xi (line 44) | function Xi(e){var n,t=e.popup,r=e.anchor,o=e.container,i=e.directions,a...
function na (line 44) | function na(){return na=Object.assign?Object.assign.bind():function(e){f...
function oa (line 44) | function oa(e){return oa="function"==typeof Symbol&&"symbol"==typeof Sym...
function ia (line 44) | function ia(e,n){return function(e){if(Array.isArray(e))return e}(e)||fu...
function aa (line 44) | function aa(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=Array...
function la (line 44) | function la(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function ca (line 44) | function ca(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function sa (line 44) | function sa(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function ua (line 44) | function ua(e,n,t){return n=fa(n),function(e,n){if(n&&("object"==oa(n)||...
function pa (line 44) | function pa(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function fa (line 44) | function fa(e){return fa=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function da (line 44) | function da(e,n){return da=Object.setPrototypeOf?Object.setPrototypeOf.b...
function ga (line 44) | function ga(e,n,t){return(n=ha(n))in e?Object.defineProperty(e,n,{value:...
function ha (line 44) | function ha(e){var n=function(e,n){if("object"!=oa(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function ka (line 44) | function ka(e){return ka="function"==typeof Symbol&&"symbol"==typeof Sym...
function Oa (line 44) | function Oa(){return Oa=Object.assign?Object.assign.bind():function(e){f...
function Ba (line 44) | function Ba(e,n,t){return(n=function(e){var n=function(e,n){if("object"!...
function Ma (line 44) | function Ma(e){return Ma="function"==typeof Symbol&&"symbol"==typeof Sym...
function Na (line 44) | function Na(){return Na=Object.assign?Object.assign.bind():function(e){f...
function Fa (line 44) | function Fa(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function La (line 44) | function La(e,n,t){return n=Ua(n),function(e,n){if(n&&("object"==Ma(n)||...
function Ha (line 44) | function Ha(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Ua (line 44) | function Ua(e){return Ua=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Wa (line 44) | function Wa(e,n){return Wa=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Ga (line 44) | function Ga(e,n,t){return(n=Ya(n))in e?Object.defineProperty(e,n,{value:...
function Ya (line 44) | function Ya(e){var n=function(e,n){if("object"!=Ma(e)||!e)return e;var t...
function qa (line 44) | function qa(){}
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Qa (line 44) | function Qa(e){return"function"==typeof Symbol&&"symbol"==typeof Symbol....
function Xa (line 44) | function Xa(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Za (line 44) | function Za(e,n,t){return el(n),function(e,n){if(n&&("object"==Qa(n)||"f...
function Ja (line 44) | function Ja(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function el (line 44) | function el(e){return Object.setPrototypeOf?Object.getPrototypeOf.bind()...
function nl (line 44) | function nl(e,n){return Object.setPrototypeOf?Object.setPrototypeOf.bind...
function tl (line 44) | function tl(e){var n=function(e,n){if("object"!=Qa(e)||!e)return e;var t...
function rl (line 44) | function rl(e,n){var t="undefined"!=typeof Symbol&&e[Symbol.iterator]||e...
function ol (line 44) | function ol(e,n){(null==n||n>e.length)&&(n=e.length);for(var t=0,r=Array...
function il (line 44) | function il(e,n){var t=arguments.length>2&&void 0!==arguments[2]&&argume...
function pl (line 44) | function pl(e){return pl="function"==typeof Symbol&&"symbol"==typeof Sym...
function fl (line 44) | function fl(){return fl=Object.assign?Object.assign.bind():function(e){f...
function dl (line 44) | function dl(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function gl (line 44) | function gl(e,n,t){return n=Al(n),function(e,n){if(n&&("object"==pl(n)||...
function hl (line 44) | function hl(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Al (line 44) | function Al(e){return Al=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function bl (line 44) | function bl(e,n){return bl=Object.setPrototypeOf?Object.setPrototypeOf.b...
function vl (line 44) | function vl(e){var n=function(e,n){if("object"!=pl(e)||!e)return e;var t...
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function Cl (line 44) | function Cl(e){return Cl="function"==typeof Symbol&&"symbol"==typeof Sym...
function wl (line 44) | function wl(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function xl (line 44) | function xl(e,n,t){return n=_l(n),function(e,n){if(n&&("object"==Cl(n)||...
function Sl (line 44) | function Sl(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function _l (line 44) | function _l(e){return _l=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function kl (line 44) | function kl(e,n){return kl=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Ol (line 44) | function Ol(e,n,t){return(n=Bl(n))in e?Object.defineProperty(e,n,{value:...
function Bl (line 44) | function Bl(e){var n=function(e,n){if("object"!=Cl(e)||!e)return e;var t...
function Il (line 44) | function Il(){return Il=Object.assign?Object.assign.bind():function(e){f...
function jl (line 44) | function jl(e){return jl="function"==typeof Symbol&&"symbol"==typeof Sym...
function zl (line 44) | function zl(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Dl (line 44) | function Dl(e,n,t){return n=Ml(n),function(e,n){if(n&&("object"==jl(n)||...
function Rl (line 44) | function Rl(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Ml (line 44) | function Ml(e){return Ml=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Nl (line 44) | function Nl(e,n){return Nl=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Fl (line 44) | function Fl(e,n,t){return(n=Ll(n))in e?Object.defineProperty(e,n,{value:...
function Ll (line 44) | function Ll(e){var n=function(e,n){if("object"!=jl(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Wl (line 44) | function Wl(e){return Wl="function"==typeof Symbol&&"symbol"==typeof Sym...
function Gl (line 44) | function Gl(){return Gl=Object.assign?Object.assign.bind():function(e){f...
function Yl (line 44) | function Yl(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function ql (line 44) | function ql(e,n,t){return n=$l(n),function(e,n){if(n&&("object"==Wl(n)||...
function Vl (line 44) | function Vl(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function $l (line 44) | function $l(e){return $l=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Kl (line 44) | function Kl(e,n){return Kl=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Ql (line 44) | function Ql(e,n,t){return(n=Xl(n))in e?Object.defineProperty(e,n,{value:...
function Xl (line 44) | function Xl(e){var n=function(e,n){if("object"!=Wl(e)||!e)return e;var t...
function Zl (line 44) | function Zl(){}
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function ec (line 44) | function ec(e){return ec="function"==typeof Symbol&&"symbol"==typeof Sym...
function nc (line 44) | function nc(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function tc (line 44) | function tc(e,n,t){return(n=rc(n))in e?Object.defineProperty(e,n,{value:...
function rc (line 44) | function rc(e){var n=function(e,n){if("object"!=ec(e)||!e)return e;var t...
function e (line 44) | function e(n){!function(e,n){if(!(e instanceof n))throw new TypeError("C...
function cc (line 44) | function cc(e){return cc="function"==typeof Symbol&&"symbol"==typeof Sym...
function sc (line 44) | function sc(){return sc=Object.assign?Object.assign.bind():function(e){f...
function uc (line 44) | function uc(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function pc (line 44) | function pc(e,n,t){return n=dc(n),function(e,n){if(n&&("object"==cc(n)||...
function fc (line 44) | function fc(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function dc (line 44) | function dc(e){return dc=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function gc (line 44) | function gc(e,n){return gc=Object.setPrototypeOf?Object.setPrototypeOf.b...
function hc (line 44) | function hc(e,n,t){return(n=Ac(n))in e?Object.defineProperty(e,n,{value:...
function Ac (line 44) | function Ac(e){var n=function(e,n){if("object"!=cc(e)||!e)return e;var t...
function t (line 44) | function t(){return function(e,n){if(!(e instanceof n))throw new TypeErr...
function Cc (line 44) | function Cc(e){return Cc="function"==typeof Symbol&&"symbol"==typeof Sym...
function wc (line 44) | function wc(){return wc=Object.assign?Object.assign.bind():function(e){f...
function xc (line 44) | function xc(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Sc (line 44) | function Sc(e,n,t){return n=kc(n),function(e,n){if(n&&("object"==Cc(n)||...
function _c (line 44) | function _c(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function kc (line 44) | function kc(e){return kc=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Oc (line 44) | function Oc(e,n){return Oc=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Bc (line 44) | function Bc(e,n,t){return(n=Pc(n))in e?Object.defineProperty(e,n,{value:...
function Pc (line 44) | function Pc(e){var n=function(e,n){if("object"!=Cc(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Ic (line 44) | function Ic(e){return Ic="function"==typeof Symbol&&"symbol"==typeof Sym...
function jc (line 44) | function jc(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function zc (line 44) | function zc(e,n,t){return n=Rc(n),function(e,n){if(n&&("object"==Ic(n)||...
function Dc (line 44) | function Dc(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Rc (line 44) | function Rc(e){return Rc=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Mc (line 44) | function Mc(e,n){return Mc=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Nc (line 44) | function Nc(e,n,t){return(n=Fc(n))in e?Object.defineProperty(e,n,{value:...
function Fc (line 44) | function Fc(e){var n=function(e,n){if("object"!=Ic(e)||!e)return e;var t...
function Lc (line 44) | function Lc(){}
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function $c (line 44) | function $c(){return $c=Object.assign?Object.assign.bind():function(e){f...
function Kc (line 44) | function Kc(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function Qc (line 44) | function Qc(e,n,t){return n=Zc(n),function(e,n){if(n&&("object"==os(n)||...
function Xc (line 44) | function Xc(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Zc (line 44) | function Zc(e){return Zc=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Jc (line 44) | function Jc(e,n){return Jc=Object.setPrototypeOf?Object.setPrototypeOf.b...
function es (line 44) | function es(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){v...
function ns (line 44) | function ns(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments...
function ts (line 44) | function ts(e,n,t){return(n=rs(n))in e?Object.defineProperty(e,n,{value:...
function rs (line 44) | function rs(e){var n=function(e,n){if("object"!=os(e)||!e)return e;var t...
function os (line 44) | function os(e){return os="function"==typeof Symbol&&"symbol"==typeof Sym...
function is (line 44) | function is(){}
function cs (line 44) | function cs(e){return Uo.isItemType(Uo.ListProps.Type.SEPARATOR,e)||Uo.i...
function ss (line 44) | function ss(e,n){var t=cs(e);return null==t||n(t)}
function us (line 44) | function us(e){if("object"===os(e)){if(e.fn)return e.fn;if(e.fuzzy)retur...
function ps (line 44) | function ps(e){return e.reduce((function(e,n){return e[n.key]=!0,e}),{})}
function fs (line 44) | function fs(e,n,t){var r=arguments.length>3&&void 0!==arguments[3]?argum...
function ds (line 44) | function ds(e,n){var t=Array.isArray(e)?e[0]:e;if(null==t)return null;fo...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function bs (line 44) | function bs(e){var n=e;if(-1===n.tabIndex)return!1;for(;n;){var t=getCom...
function e (line 44) | function e(e){var n=e.trapElement,t=e.navigationKeys,r=e.interactiveElem...
function t (line 44) | function t(){this.constructor=e}
function t (line 44) | function t(){return null!==n&&n.apply(this,arguments)||this}
function Ps (line 44) | function Ps(e){return Ps="function"==typeof Symbol&&"symbol"==typeof Sym...
function Ts (line 44) | function Ts(){return Ts=Object.assign?Object.assign.bind():function(e){f...
function Is (line 44) | function Is(e,n){for(var t=0;t<n.length;t++){var r=n[t];r.enumerable=r.e...
function js (line 44) | function js(e,n,t){return n=Ds(n),function(e,n){if(n&&("object"==Ps(n)||...
function zs (line 44) | function zs(){try{var e=!Boolean.prototype.valueOf.call(Reflect.construc...
function Ds (line 44) | function Ds(e){return Ds=Object.setPrototypeOf?Object.getPrototypeOf.bin...
function Rs (line 44) | function Rs(e,n){return Rs=Object.setPrototypeOf?Object.setPrototypeOf.b...
function Ms (line 44) | function Ms(e,n,t){return(n=Ns(n))in e?Object.defineProperty(e,n,{value:...
function Ns (line 44) | function Ns(e){var n=function(e,n){if("object"!=Ps(e)||!e)return e;var t...
function t (line 44) | function t(){var e;!function(e,n){if(!(e instanceof n))throw new TypeErr...
function Us (line 44) | function Us(){return Us=Object.assign?Object.assign.bind():function(e){f...
function e (line 44) | function e(){}
function e (line 44) | function e(){var e,n=this;this.registerHotkeyWithAccel=function(e,t){var...
function tu (line 44) | function tu(){var e=document.querySelector('[data-test="ring-input-clear...
function ru (line 44) | function ru(){document.body.style.overflow="",cu()}
function ou (line 44) | function ou(e){var n=e.key;"Tab"===n&&(e.preventDefault(),e.stopPropagat...
function iu (line 44) | function iu(){var e=document.querySelector('[data-test-custom="ring-sele...
function au (line 44) | function au(e){var n=e.target;if("search-close-button"===(null==n?void 0...
function cu (line 44) | function cu(){null!==lu&&(lu.disconnect(),lu=null)}
function su (line 44) | function su(){var e=document.querySelector(".ReactVirtualized__Grid");e&...
FILE: docs/scripts/navigation-loader.js
constant TOC_STATE_KEY_PREFIX (line 4) | const TOC_STATE_KEY_PREFIX = 'TOC_STATE::';
constant TOC_CONTAINER_ID (line 5) | const TOC_CONTAINER_ID = 'sideMenu';
constant TOC_SCROLL_CONTAINER_ID (line 6) | const TOC_SCROLL_CONTAINER_ID = 'leftColumn';
constant TOC_PART_CLASS (line 7) | const TOC_PART_CLASS = 'toc--part';
constant TOC_PART_HIDDEN_CLASS (line 8) | const TOC_PART_HIDDEN_CLASS = 'toc--part_hidden';
constant TOC_LINK_CLASS (line 9) | const TOC_LINK_CLASS = 'toc--link';
constant TOC_SKIP_LINK_CLASS (line 10) | const TOC_SKIP_LINK_CLASS = 'toc--skip-link';
function displayToc (line 13) | function displayToc() {
function renderToc (line 26) | function renderToc(tocHTML) {
function updateTocLinks (line 33) | function updateTocLinks() {
function collapseTocParts (line 44) | function collapseTocParts() {
function saveTocScrollTop (line 119) | function saveTocScrollTop() {
function restoreTocScrollTop (line 127) | function restoreTocScrollTop() {
function initTocScrollListener (line 137) | function initTocScrollListener() {
function preventScrollBySpaceKey (line 144) | function preventScrollBySpaceKey(event) {
function resetTocState (line 151) | function resetTocState() {
function initLogoClickListener (line 160) | function initLogoClickListener() {
function handleTocButtonClick (line 185) | function handleTocButtonClick(event, navId) {
FILE: docs/scripts/platform-content-handler.js
function scrollToElementInContent (line 60) | function scrollToElementInContent(element) {
function handleAnchor (line 84) | function handleAnchor() {
function filterButtonHandler (line 133) | function filterButtonHandler(event) {
function initializeFiltering (line 144) | function initializeFiltering() {
function filterSourceset (line 163) | function filterSourceset(sourceset) {
function unfilterSourceset (line 169) | function unfilterSourceset(sourceset) {
function addSourcesetFilterToCache (line 182) | function addSourcesetFilterToCache(sourceset) {
function removeSourcesetFilterFromCache (line 192) | function removeSourcesetFilterFromCache(sourceset) {
function refreshSourcesetsCache (line 200) | function refreshSourcesetsCache() {
function togglePlatformDependent (line 205) | function togglePlatformDependent(e, container) {
function refreshFiltering (line 231) | function refreshFiltering() {
function refreshNoContentNotification (line 245) | function refreshNoContentNotification() {
function refreshPlatformTabs (line 265) | function refreshPlatformTabs() {
function refreshFilterButtons (line 289) | function refreshFilterButtons() {
FILE: docs/scripts/prism.js
function u (line 3) | function u(e){s.highlightedCode=e,a.hooks.run("before-insert",s),s.eleme...
function i (line 3) | function i(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=...
function l (line 3) | function l(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a...
function o (line 3) | function o(e,n,t,r,s,g){for(var f in t)if(t.hasOwnProperty(f)&&t[f]){var...
function s (line 3) | function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e...
function u (line 3) | function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a...
function c (line 3) | function c(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;n.ne...
function f (line 3) | function f(){a.manual||a.highlightAll()}
function d (line 12) | function d(e){if(function(e){return!n||"span"!==e.nodeName.toLowerCase()...
function r (line 12) | function r(e){for(var n=0,o=e.childNodes.length;n<o;n++){var r=e.childNo...
Condensed preview — 262 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (5,117K chars).
[
{
"path": ".claude/commands/bump-version.md",
"chars": 1608,
"preview": "---\ndescription: Upgrade the library version across all files that contain it\nargument-hint: \"<new-version>\"\n---\n\nBump t"
},
{
"path": ".claude/commands/ci-local.md",
"chars": 2095,
"preview": "---\ndescription: Run the full CI pipeline locally (spotless + all platform builds) to catch failures before pushing\nargu"
},
{
"path": ".claude/commands/dokka.md",
"chars": 1001,
"preview": "---\ndescription: Generate API documentation using Dokka V2 and sync to docs folder\n---\n\nGenerate HTML API documentation "
},
{
"path": ".claude/commands/spotless.md",
"chars": 1051,
"preview": "---\ndescription: Run Spotless formatting check and auto-fix any violations\nargument-hint: \"[check|apply]\"\n---\n\nRun Spotl"
},
{
"path": ".claude/skills/compose-expert/SKILL.md",
"chars": 10970,
"preview": "---\nname: compose-expert\ndescription: >\n Compose and Compose Multiplatform expert skill for UI development across Andro"
},
{
"path": ".claude/skills/compose-expert/references/accessibility.md",
"chars": 11168,
"preview": "# Accessibility Reference\n\n## Semantics — Exposing UI to Accessibility Services\n\nSemantics describe UI elements to scree"
},
{
"path": ".claude/skills/compose-expert/references/animation.md",
"chars": 42264,
"preview": "# Animation in Jetpack Compose\n\nReference: `androidx/compose/animation/animation/src/commonMain/kotlin/androidx/compose/"
},
{
"path": ".claude/skills/compose-expert/references/atomic-design.md",
"chars": 10919,
"preview": "# Atomic Design System Reference\n\nBuilding reusable, hierarchical component systems in Jetpack Compose and Compose Multi"
},
{
"path": ".claude/skills/compose-expert/references/auto-init.md",
"chars": 1609,
"preview": "# Auto-Init: Compose Project Detection\n\nActivate on `session_start`. Detect whether the current project uses Compose and"
},
{
"path": ".claude/skills/compose-expert/references/composition-locals.md",
"chars": 6906,
"preview": "# CompositionLocals: Implicit Data Passing in Jetpack Compose\n\nCompositionLocals provide a way to pass data implicitly d"
},
{
"path": ".claude/skills/compose-expert/references/deprecated-patterns.md",
"chars": 8427,
"preview": "# Deprecated Patterns & API Migrations in Jetpack Compose\n\nThis guide covers major API changes and deprecations in Compo"
},
{
"path": ".claude/skills/compose-expert/references/design-to-compose.md",
"chars": 23914,
"preview": "# Design-to-Compose Translation Reference\n\nTranslating visual designs (Figma mockups, screenshots, wireframes) into prod"
},
{
"path": ".claude/skills/compose-expert/references/lists-scrolling.md",
"chars": 13891,
"preview": "# Lists and Scrolling in Jetpack Compose\n\nEfficient list rendering and scrolling are core to responsive mobile UIs. Jetp"
},
{
"path": ".claude/skills/compose-expert/references/material3-motion.md",
"chars": 12410,
"preview": "# Material 3 Motion\n\nSource: `compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/Motion"
},
{
"path": ".claude/skills/compose-expert/references/modifiers.md",
"chars": 12204,
"preview": "# Jetpack Compose Modifiers Reference\n\nModifiers are the primary way to decorate or augment a composable. They apply lay"
},
{
"path": ".claude/skills/compose-expert/references/multiplatform.md",
"chars": 20967,
"preview": "# Compose Multiplatform (CMP) Reference\n\nReference: `compose-multiplatform` (JetBrains), `androidx.compose` (Google)\n\n##"
},
{
"path": ".claude/skills/compose-expert/references/navigation.md",
"chars": 9922,
"preview": "# Navigation in Jetpack Compose\n\nReference: `androidx/navigation/navigation-compose/src/commonMain/kotlin/androidx/navig"
},
{
"path": ".claude/skills/compose-expert/references/performance.md",
"chars": 15777,
"preview": "# Performance Optimization Reference\n\n## Three Phases: Composition, Layout, Drawing\n\nEvery frame consists of three phase"
},
{
"path": ".claude/skills/compose-expert/references/platform-specifics.md",
"chars": 18302,
"preview": "# Platform-Specific APIs and Gotchas (Compose Multiplatform)\n\nCompose Multiplatform shares most UI code across platforms"
},
{
"path": ".claude/skills/compose-expert/references/pr-review.md",
"chars": 12941,
"preview": "# PR Review Mode\n\nActivate when: input contains a GitHub PR URL (`github.com/.+/pull/\\d+`) or explicit review\nphrases: \""
},
{
"path": ".claude/skills/compose-expert/references/production-crash-playbook.md",
"chars": 24792,
"preview": "# Production Crash Playbook for Jetpack Compose\n\nReal-world crash patterns observed in Compose applications at scale. Ea"
},
{
"path": ".claude/skills/compose-expert/references/side-effects.md",
"chars": 12822,
"preview": "# Jetpack Compose Side Effects Reference\n\nCompose is declarative, but apps must interact with the imperative world: laun"
},
{
"path": ".claude/skills/compose-expert/references/source-code/cmp-source.md",
"chars": 18257,
"preview": "# Compose Multiplatform Source Reference\n\n> API signatures from [JetBrains/compose-multiplatform-core](https://github.co"
},
{
"path": ".claude/skills/compose-expert/references/source-code/foundation-source.md",
"chars": 574348,
"preview": "# Compose Foundation Source Reference\n\n## File: compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/fou"
},
{
"path": ".claude/skills/compose-expert/references/source-code/material3-source.md",
"chars": 856109,
"preview": "# Compose Material3 Source Reference\n\n## File: compose/material3/material3/src/commonMain/kotlin/androidx/compose/materi"
},
{
"path": ".claude/skills/compose-expert/references/source-code/navigation-source.md",
"chars": 121696,
"preview": "# Navigation Compose Source Reference\n\n## File: navigation/navigation-compose/src/androidMain/kotlin/androidx/navigation"
},
{
"path": ".claude/skills/compose-expert/references/source-code/runtime-source.md",
"chars": 459774,
"preview": "# Compose Runtime Source Reference\n\n## File: compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Comp"
},
{
"path": ".claude/skills/compose-expert/references/source-code/ui-source.md",
"chars": 320612,
"preview": "# Compose UI Source Reference\n\n## File: compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposi"
},
{
"path": ".claude/skills/compose-expert/references/state-management.md",
"chars": 16729,
"preview": "# Jetpack Compose State Management Reference\n\n## State Fundamentals\n\nState in Compose is observable data that triggers r"
},
{
"path": ".claude/skills/compose-expert/references/styles-experimental.md",
"chars": 14753,
"preview": "# Compose Styles API (Experimental)\n\n> `@ExperimentalFoundationStyleApi` — `androidx.compose.foundation:foundation:1.11."
},
{
"path": ".claude/skills/compose-expert/references/theming-material3.md",
"chars": 8120,
"preview": "# Material 3 Theming Reference\n\n## MaterialTheme Basics\n\n`MaterialTheme` is the root provider for design tokens in Compo"
},
{
"path": ".claude/skills/compose-expert/references/tv-compose.md",
"chars": 30865,
"preview": "# Compose for TV (Android TV / Google TV)\n\nReference for building Android TV apps using Jetpack Compose with `androidx.t"
},
{
"path": ".claude/skills/compose-expert/references/view-composition.md",
"chars": 20029,
"preview": "# Jetpack Compose View Composition Reference\n\n## Composable Function Naming Conventions\n\nNames communicate intent. Follo"
},
{
"path": ".claude/skills/edge-to-edge/SKILL.md",
"chars": 14418,
"preview": "---\nname: edge-to-edge\ndescription: |-\n Use this skill to migrate your Jetpack Compose app to add adaptive edge-to-edge"
},
{
"path": ".claude/skills/r8-analyzer/SKILL.md",
"chars": 3631,
"preview": "---\nname: r8-analyzer\ndescription: |-\n Analyzes Android build files and R8 keep rules to identify redundancies, broad p"
},
{
"path": ".claude/skills/r8-analyzer/references/CONFIGURATION.md",
"chars": 1522,
"preview": "To achieve maximum utilization of R8, the codebase must be configured correctly\ndepending on the build script language ("
},
{
"path": ".claude/skills/r8-analyzer/references/KEEP-RULES-IMPACT-HIERARCHY.md",
"chars": 3156,
"preview": "Keep rules prevent optimization of R8, these rules are listed in the order of\nthe scope of what it retains in the codeba"
},
{
"path": ".claude/skills/r8-analyzer/references/REDUNDANT-RULES.md",
"chars": 8676,
"preview": "This document outlines common \"bad\" or redundant keep rules for standard Android\ndevelopment and popular libraries. Mode"
},
{
"path": ".claude/skills/r8-analyzer/references/REFLECTION-GUIDE.md",
"chars": 4385,
"preview": "A categorized summary of the keep rule examples, including the code patterns to\nlook for (imports/usage) and the corresp"
},
{
"path": ".claude/skills/r8-analyzer/references/android/topic/performance/app-optimization/enable-app-optimization.md",
"chars": 11729,
"preview": "For the best user experience, you should optimize your app to make it as small\nand fast as possible. Our app optimizer, "
},
{
"path": ".editorconfig",
"chars": 396,
"preview": "# https://editorconfig.org/\n# This configuration is used by ktlint when spotless invokes it\n\n[*]\n# Most of the standard "
},
{
"path": ".github/workflows/build.yml",
"chars": 2705,
"preview": "name: Build\n\non:\n push:\n branches: [ main ]\n pull_request:\n branches: [ main ]\n types: [ opened, synchronize,"
},
{
"path": ".github/workflows/publish.yml",
"chars": 3404,
"preview": "name: Publish to Maven Central\n\non:\n workflow_dispatch:\n inputs:\n version:\n description: 'Version to pub"
},
{
"path": ".github/workflows/setup/ios-setup/action.yml",
"chars": 413,
"preview": "name: iOS set up\ndescription: Sets up Kotlin Native and Cocoapods\nruns:\n using: \"composite\"\n steps:\n - shell: bash\n"
},
{
"path": ".github/workflows/setup/java-setup/action.yml",
"chars": 1108,
"preview": "name: Java and Gradle Job Setup\ndescription: Sets up Java and Gradle\nruns:\n using: \"composite\"\n steps:\n # Setup jav"
},
{
"path": ".gitignore",
"chars": 1220,
"preview": "# Built application files\n*.apk\n*.aar\n*.ap_\n*.aab\n\n# Files for the ART/Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Ko"
},
{
"path": "CLAUDE.md",
"chars": 6382,
"preview": "# CLAUDE.md\n \nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n \n#"
},
{
"path": "CONTRIBUTING.md",
"chars": 678,
"preview": "## How to contribute\nWe'd love to accept your patches and contributions to this project. There are just a few small guid"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2022 Pushpal Roy\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "PUBLISHING.md",
"chars": 3984,
"preview": "# Publishing to Maven Central\n\nJetLime is published to Maven Central via a manual GitHub Actions workflow (`publish.yml`"
},
{
"path": "README.md",
"chars": 17751,
"preview": "# JetLime 🍋\n\n> A simple yet highly customizable UI library to show a timeline view in Compose Multiplatform.\n\n[![Jetbrai"
},
{
"path": "build.gradle.kts",
"chars": 1563,
"preview": "plugins {\n alias(libs.plugins.android.application) apply false\n alias(libs.plugins.android.library) apply false\n alia"
},
{
"path": "docs/index.html",
"chars": 8803,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-animation/animation-spec.html",
"chars": 7899,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-animation/equals.html",
"chars": 9295,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-animation/hash-code.html",
"chars": 8203,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-animation/index.html",
"chars": 17690,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-animation/initial-value.html",
"chars": 7670,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-animation/target-value.html",
"chars": 7664,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/-companion/-default.html",
"chars": 7776,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/-companion/-e-m-p-t-y.html",
"chars": 7742,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/-companion/custom.html",
"chars": 10055,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/-companion/filled.html",
"chars": 9068,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/-companion/index.html",
"chars": 15399,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/equals.html",
"chars": 8965,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/fill-percent.html",
"chars": 7691,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/hash-code.html",
"chars": 8181,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/icon.html",
"chars": 7652,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/index.html",
"chars": 26219,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/is-custom.html",
"chars": 8439,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/is-empty-or-filled.html",
"chars": 8590,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/is-filled.html",
"chars": 8439,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/tint.html",
"chars": 7640,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-point-type/type.html",
"chars": 7589,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/-companion/dynamic.html",
"chars": 9568,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/-companion/index.html",
"chars": 10151,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/equals.html",
"chars": 8943,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/hash-code.html",
"chars": 8179,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/index.html",
"chars": 19022,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/is-not-end.html",
"chars": 8426,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/is-not-start.html",
"chars": 8116,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-event-position/name.html",
"chars": 7587,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-horizontal-alignment/-b-o-t-t-o-m/index.html",
"chars": 11136,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-horizontal-alignment/-t-o-p/index.html",
"chars": 11121,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-horizontal-alignment/entries.html",
"chars": 7962,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-horizontal-alignment/index.html",
"chars": 19642,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-horizontal-alignment/value-of.html",
"chars": 8809,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-horizontal-alignment/values.html",
"chars": 8044,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-items-list/-items-list.html",
"chars": 8359,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-items-list/equals.html",
"chars": 9048,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-items-list/hash-code.html",
"chars": 8131,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-items-list/index.html",
"chars": 16932,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-items-list/items.html",
"chars": 7699,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-column.html",
"chars": 15941,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-defaults/column-style.html",
"chars": 12908,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-defaults/index.html",
"chars": 21353,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-defaults/line-gradient-brush.html",
"chars": 11806,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-defaults/line-solid-brush.html",
"chars": 9311,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-defaults/row-style.html",
"chars": 12893,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-defaults/event-style.html",
"chars": 15547,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-defaults/index.html",
"chars": 15711,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-defaults/point-animation.html",
"chars": 11003,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/equals.html",
"chars": 8991,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/hash-code.html",
"chars": 8178,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/index.html",
"chars": 30150,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-animation.html",
"chars": 7695,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-color.html",
"chars": 7670,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-fill-color.html",
"chars": 7719,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-placement.html",
"chars": 7647,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-radius.html",
"chars": 7666,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-stroke-color.html",
"chars": 7731,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-stroke-width.html",
"chars": 7721,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/point-type.html",
"chars": 7618,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/position.html",
"chars": 7655,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/set-point-placement.html",
"chars": 9028,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event-style/set-position.html",
"chars": 9210,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-event.html",
"chars": 11216,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-extended-event.html",
"chars": 14020,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-row.html",
"chars": 15885,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/content-distance.html",
"chars": 8181,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/equals.html",
"chars": 9032,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/hash-code.html",
"chars": 8212,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/index.html",
"chars": 26055,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/item-spacing.html",
"chars": 8140,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/line-brush.html",
"chars": 8138,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/line-horizontal-alignment.html",
"chars": 8257,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/line-thickness.html",
"chars": 8154,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/line-vertical-alignment.html",
"chars": 8237,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-jet-lime-style/path-effect.html",
"chars": 8217,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-local-jet-lime-style.html",
"chars": 8171,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-point-placement/-c-e-n-t-e-r/index.html",
"chars": 11206,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-point-placement/-e-n-d/index.html",
"chars": 11208,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-point-placement/-s-t-a-r-t/index.html",
"chars": 11205,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-point-placement/entries.html",
"chars": 7947,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-point-placement/index.html",
"chars": 20863,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-point-placement/value-of.html",
"chars": 8794,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-point-placement/values.html",
"chars": 8029,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-vertical-alignment/-l-e-f-t/index.html",
"chars": 11030,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-vertical-alignment/-r-i-g-h-t/index.html",
"chars": 10979,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-vertical-alignment/entries.html",
"chars": 7956,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-vertical-alignment/index.html",
"chars": 19526,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-vertical-alignment/value-of.html",
"chars": 8803,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/-vertical-alignment/values.html",
"chars": 8038,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/com.pushpal.jetlime/index.html",
"chars": 47200,
"preview": "<!DOCTYPE html>\n<html class=\"no-js\" lang=\"en\">\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-sca"
},
{
"path": "docs/jetlime/package-list",
"chars": 16737,
"preview": "$dokka.format:html-v1\n$dokka.linkExtension:html\n$dokka.location:com.pushpal.jetlime////PointingToDeclaration/\u001fjetlime/co"
},
{
"path": "docs/navigation.html",
"chars": 15875,
"preview": "<div class=\"toc--part\" id=\"jetlime-nav-submenu\" pageId=\"jetlime::////PointingToDeclaration//538146535\" data-nesting-leve"
},
{
"path": "docs/scripts/main.js",
"chars": 692045,
"preview": "(()=>{var e={1817:e=>{e.exports='<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 14 14\"><pat"
},
{
"path": "docs/scripts/navigation-loader.js",
"chars": 6527,
"preview": "/*\n * Copyright 2014-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.\n */\nconst TOC_"
},
{
"path": "docs/scripts/pages.json",
"chars": 30561,
"preview": "[{\"name\":\"BOTTOM\",\"description\":\"com.pushpal.jetlime.HorizontalAlignment.BOTTOM\",\"location\":\"jetlime/com.pushpal.jetlime"
},
{
"path": "docs/scripts/platform-content-handler.js",
"chars": 11204,
"preview": "/*\n * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.\n */\n\nfiltering"
},
{
"path": "docs/scripts/prism.js",
"chars": 25995,
"preview": "/* PrismJS 1.29.0\nhttps://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+java+javadoc+java"
},
{
"path": "docs/scripts/safe-local-storage_blocking.js",
"chars": 2327,
"preview": "/*\n * Copyright 2014-2025 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.\n */\n/** When D"
},
{
"path": "docs/scripts/sourceset_dependencies.js",
"chars": 595,
"preview": "sourceset_dependencies='{\":jetlime/androidMain\":[\":jetlime/commonMain\"],\":jetlime/androidRelease\":[\":jetlime/commonMain\""
},
{
"path": "docs/styles/logo-styles.css",
"chars": 242,
"preview": "/*\n * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.\n */\n\n:root {\n "
},
{
"path": "docs/styles/main.css",
"chars": 19034,
"preview": "/*!\n * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.\n *//*!\n * Cop"
},
{
"path": "docs/styles/prism.css",
"chars": 2656,
"preview": "/*\n * Custom Dokka styles\n */\ncode .token {\n white-space: pre;\n}\n\n/**\n * Styles based on webhelp's prism.js styles\n *"
},
{
"path": "docs/styles/style.css",
"chars": 9053,
"preview": "/*\n * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.\n */\n\n/* --- ro"
},
{
"path": "dokkaModule.md",
"chars": 354,
"preview": "Module jetlime\n\n# JetLime Module Documentation\nJetLime is a Kotlin Multiplatform timeline UI library for Compose.\n\n## Ov"
},
{
"path": "dokkaPackage.md",
"chars": 310,
"preview": "Package com.pushpal.jetlime\n\n# JetLime Core Package\nContains composables, styles, timeline rendering, and helper utiliti"
},
{
"path": "gradle/libs.versions.toml",
"chars": 1984,
"preview": "[versions]\nkotlin = \"2.3.20\"\nagp = \"8.13.1\"\nactivityCompose = \"1.13.0\"\ncompose-plugin = \"1.10.3\"\nkotlinxCollectionsImmut"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 282,
"preview": "#Thu Feb 29 20:52:44 CST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://"
},
{
"path": "gradle.properties",
"chars": 1274,
"preview": "# Code style\nkotlin.code.style=official\n# Gradle\norg.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm."
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2763,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "jetlime/.gitignore",
"chars": 6,
"preview": "/build"
},
{
"path": "jetlime/build.gradle.kts",
"chars": 5238,
"preview": "plugins {\n alias(libs.plugins.android.library)\n alias(libs.plugins.kotlin.multiplatform)\n alias(libs.plugins.jetbrain"
},
{
"path": "jetlime/consumer-rules.pro",
"chars": 0,
"preview": ""
},
{
"path": "jetlime/dokkaModule.md",
"chars": 426,
"preview": "Module\njetlime\n\n# JetLime\nA Kotlin Multiplatform library providing timeline UI components for Compose (Android, Desktop,"
},
{
"path": "jetlime/dokkaPackage.md",
"chars": 342,
"preview": "Package\ncom.pushpal.jetlime\n\n# Core Package\nContains composables, styles, event rendering logic and helper utilities for"
},
{
"path": "jetlime/jetlime.podspec",
"chars": 2215,
"preview": "Pod::Spec.new do |spec|\n spec.name = 'jetlime'\n spec.version = '4.3.0'\n sp"
},
{
"path": "jetlime/proguard-rules.pro",
"chars": 754,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "jetlime/src/androidMain/AndroidManifest.xml",
"chars": 121,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n</manifest"
},
{
"path": "jetlime/src/androidTest/java/com/pushpal/jetlime/ExampleInstrumentedTest.kt",
"chars": 1776,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/androidTest/java/com/pushpal/jetlime/JetLimeColumnTest.kt",
"chars": 9665,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/androidTest/java/com/pushpal/jetlime/JetLimeRowTest.kt",
"chars": 11502,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/EventPointAnimation.kt",
"chars": 3250,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/EventPointType.kt",
"chars": 5084,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/EventPosition.kt",
"chars": 3777,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/ItemsList.kt",
"chars": 2472,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/JetLimeDefaults.kt",
"chars": 7317,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/JetLimeEvent.kt",
"chars": 19525,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/JetLimeEventDefaults.kt",
"chars": 5452,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/JetLimeEventStyle.kt",
"chars": 5609,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/JetLimeExtendedEvent.kt",
"chars": 12535,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/JetLimeList.kt",
"chars": 7605,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/commonMain/kotlin/com/pushpal/jetlime/JetLimeStyle.kt",
"chars": 6157,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "jetlime/src/test/java/com/pushpal/jetlime/ExampleUnitTest.kt",
"chars": 1458,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "public-key.asc",
"chars": 2493,
"preview": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQGNBGkiCbkBDACbV6/cBB9+cl7RsAdJw8vbZqOew3KgC33rdwluRILaCYpNBkNs\ny19NCD4jqITolSIJa"
},
{
"path": "sample/composeApp/build.gradle.kts",
"chars": 5157,
"preview": "import org.jetbrains.compose.desktop.application.dsl.TargetFormat\nimport org.jetbrains.kotlin.gradle.ExperimentalKotlinG"
},
{
"path": "sample/composeApp/composeApp.podspec",
"chars": 2226,
"preview": "Pod::Spec.new do |spec|\n spec.name = 'composeApp'\n spec.version = '1.0.0'\n "
},
{
"path": "sample/composeApp/src/androidMain/AndroidManifest.xml",
"chars": 875,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n <applica"
},
{
"path": "sample/composeApp/src/androidMain/kotlin/com/pushpal/jetlime/sample/JetLimePreviews.kt",
"chars": 5639,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "sample/composeApp/src/androidMain/kotlin/com/pushpal/jetlime/sample/MainActivity.kt",
"chars": 1613,
"preview": "/*\n* MIT License\n*\n* Copyright (c) 2024 Pushpal Roy\n*\n* Permission is hereby granted, free of charge, to any person obta"
},
{
"path": "sample/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml",
"chars": 5605,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:wi"
},
{
"path": "sample/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml",
"chars": 1702,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.com/aapt\"\n "
},
{
"path": "sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "sample/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 272,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "sample/composeApp/src/androidMain/res/values/strings.xml",
"chars": 77,
"preview": "<resources>\n <string name=\"app_name\">JetLime Samples</string>\n</resources>"
},
{
"path": "sample/composeApp/src/androidMain/res/values/themes.xml",
"chars": 107,
"preview": "<resources>\n <style name=\"Theme.JetLime\" parent=\"android:Theme.Material.Light.NoActionBar\" />\n</resources>"
},
{
"path": "sample/composeApp/src/androidMain/res/values-night/themes.xml",
"chars": 101,
"preview": "<resources>\n <style name=\"Theme.JetLime\" parent=\"android:Theme.Material.NoActionBar\" />\n</resources>"
}
]
// ... and 62 more files (download for full content)
About this extraction
This page contains the full source code of the pushpalroy/jetlime GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 262 files (4.7 MB), approximately 1.2M tokens, and a symbol index with 800 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.