diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 24f7b6af..028e4eb3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,6 +107,12 @@ jobs: cache: "npm" cache-dependency-path: vscode-extension/package-lock.json + - name: Build vendored mynah-ui + working-directory: vendor/mynah-ui + run: | + npm ci + npm run build + - name: Install dependencies working-directory: vscode-extension run: npm ci diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml new file mode 100644 index 00000000..60fd6616 --- /dev/null +++ b/.github/workflows/release-binaries.yml @@ -0,0 +1,189 @@ +name: Release Binaries + +# Triggered when release-plz creates a release for symposium-acp-agent +on: + release: + types: [published] + +jobs: + build-binaries: + name: Build ${{ matrix.target }} + if: startsWith(github.ref_name, 'symposium-acp-agent-v') + strategy: + matrix: + include: + - target: x86_64-apple-darwin + os: macos-latest + artifact: symposium-darwin-x64 + - target: aarch64-apple-darwin + os: macos-latest + artifact: symposium-darwin-arm64 + - target: x86_64-unknown-linux-gnu + os: ubuntu-latest + artifact: symposium-linux-x64 + - target: aarch64-unknown-linux-gnu + os: ubuntu-latest + artifact: symposium-linux-arm64 + - target: x86_64-unknown-linux-musl + os: ubuntu-latest + artifact: symposium-linux-x64-musl + - target: x86_64-pc-windows-msvc + os: windows-latest + artifact: symposium-windows-x64 + + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install cross-compilation tools (Linux ARM64) + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: Install musl tools + if: matrix.target == 'x86_64-unknown-linux-musl' + run: | + sudo apt-get update + sudo apt-get install -y musl-tools + + - name: Build + run: cargo build --release --target ${{ matrix.target }} -p symposium-acp-agent + env: + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + + - name: Package (Unix) + if: runner.os != 'Windows' + run: | + mkdir -p dist + cp target/${{ matrix.target }}/release/symposium-acp-agent dist/ + cd dist + tar -czvf ${{ matrix.artifact }}.tar.gz symposium-acp-agent + + - name: Package (Windows) + if: runner.os == 'Windows' + run: | + mkdir dist + copy target\${{ matrix.target }}\release\symposium-acp-agent.exe dist\ + cd dist + 7z a ${{ matrix.artifact }}.zip symposium-acp-agent.exe + + - name: Upload to release + uses: softprops/action-gh-release@v1 + with: + files: dist/${{ matrix.artifact }}.* + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload artifact for VSCode job + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: dist/symposium-acp-agent* + + build-vscode-extensions: + name: Build VSCode Extensions + needs: build-binaries + runs-on: ubuntu-latest + + strategy: + matrix: + include: + - vscode-target: darwin-arm64 + artifact: symposium-darwin-arm64 + binary: symposium-acp-agent + - vscode-target: darwin-x64 + artifact: symposium-darwin-x64 + binary: symposium-acp-agent + - vscode-target: linux-x64 + artifact: symposium-linux-x64 + binary: symposium-acp-agent + - vscode-target: linux-arm64 + artifact: symposium-linux-arm64 + binary: symposium-acp-agent + - vscode-target: win32-x64 + artifact: symposium-windows-x64 + binary: symposium-acp-agent.exe + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Download binary + uses: actions/download-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: vscode-extension/bin/${{ matrix.vscode-target }} + + - name: Make binary executable + if: runner.os != 'Windows' + run: chmod +x vscode-extension/bin/${{ matrix.vscode-target }}/${{ matrix.binary }} + + - name: Build vendored mynah-ui + working-directory: vendor/mynah-ui + run: | + npm ci + npm run build + + - name: Install extension dependencies + working-directory: vscode-extension + run: npm ci + + - name: Package extension + working-directory: vscode-extension + run: npx vsce package --target ${{ matrix.vscode-target }} + + - name: Upload to release + uses: softprops/action-gh-release@v1 + with: + files: vscode-extension/*.vsix + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload vsix artifact + uses: actions/upload-artifact@v4 + with: + name: vsix-${{ matrix.vscode-target }} + path: vscode-extension/*.vsix + + publish-vscode-marketplace: + name: Publish to VSCode Marketplace + needs: build-vscode-extensions + runs-on: ubuntu-latest + steps: + - name: Download all vsix artifacts + uses: actions/download-artifact@v4 + with: + pattern: vsix-* + merge-multiple: true + + - name: Publish to marketplace + run: npx vsce publish --packagePath *.vsix + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + + publish-open-vsx: + name: Publish to Open VSX + needs: build-vscode-extensions + runs-on: ubuntu-latest + steps: + - name: Download all vsix artifacts + uses: actions/download-artifact@v4 + with: + pattern: vsix-* + merge-multiple: true + + - name: Publish to Open VSX + run: npx ovsx publish *.vsix + env: + OVSX_PAT: ${{ secrets.OVSX_PAT }} diff --git a/.github/workflows/release-plz.yml b/.github/workflows/release-plz.yml new file mode 100644 index 00000000..a099ff1a --- /dev/null +++ b/.github/workflows/release-plz.yml @@ -0,0 +1,52 @@ +name: Release-plz + +on: + push: + branches: + - main + +jobs: + release-plz-release: + name: Release-plz release + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'symposium-dev' }} + permissions: + contents: write + id-token: write + steps: + - &checkout + name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: true + token: ${{ secrets.RELEASE_PLZ_TOKEN }} + - &install-rust + name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} + + release-plz-pr: + name: Release-plz PR + runs-on: ubuntu-latest + if: ${{ github.repository_owner == 'symposium-dev' }} + permissions: + pull-requests: write + contents: write + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - *checkout + - *install-rust + - name: Run release-plz + uses: release-plz/action@v0.5 + with: + command: release-pr + env: + GITHUB_TOKEN: ${{ secrets.RELEASE_PLZ_TOKEN }} diff --git a/book.toml b/book.toml index 21be0c74..33f8350c 100644 --- a/book.toml +++ b/book.toml @@ -7,9 +7,6 @@ title = "Symposium" [preprocessor.mermaid] command = "mdbook-mermaid" -[preprocessor.rfd] -command = "cargo run -p md-rfd-preprocessor --" - [output] [output.html] diff --git a/md/SUMMARY.md b/md/SUMMARY.md index 27b495ad..4e07b469 100644 --- a/md/SUMMARY.md +++ b/md/SUMMARY.md @@ -18,6 +18,7 @@ # Design and implementation - [Overview](./design/implementation-overview.md) +- [Distribution](./design/distribution.md) - [Components](./design/components.md) - [Rust Crate Sources](./design/rust-crate-sources.md) - [VSCode Extension](./design/vscode-extension/architecture.md) @@ -27,6 +28,7 @@ - [Webview Lifecycle](./design/vscode-extension/webview-lifecycle.md) - [Testing](./design/vscode-extension/testing.md) - [Testing Implementation](./design/vscode-extension/testing-implementation.md) + - [Packaging](./design/vscode-extension/packaging.md) - [Implementation Status](./design/vscode-extension/implementation-status.md) # References diff --git a/md/design/distribution.md b/md/design/distribution.md new file mode 100644 index 00000000..70ad4eab --- /dev/null +++ b/md/design/distribution.md @@ -0,0 +1,79 @@ +# Distribution + +This chapter documents how Symposium is released and distributed across platforms. + +## Release Orchestration + +Releases are triggered by [release-plz](https://release-plz.dev/), which: + +1. Creates a release PR when changes accumulate on `main` +2. When merged, publishes to crates.io and creates GitHub releases with tags + +The `symposium-acp-agent-v*` tag triggers the binary release workflow. + +## Distribution Channels + +``` +release-plz creates tag + ↓ +┌───────────────────────────────────────┐ +│ GitHub Release │ +│ - Binary archives (all platforms) │ +│ - VSCode .vsix files │ +│ - Source reference │ +└───────────────────────────────────────┘ + ↓ +┌─────────────┬─────────────┬───────────┐ +│ crates.io │ VSCode │ Zed │ +│ │ Marketplace │Extensions │ +│ │ + Open VSX │ │ +└─────────────┴─────────────┴───────────┘ +``` + +### crates.io + +The Rust crates are published directly by release-plz. Users can install via: +```bash +cargo install symposium-acp-agent +``` + +### VSCode Marketplace / Open VSX + +Platform-specific extensions are built and published automatically. Each platform gets its own ~7MB extension containing only that platform's binary. + +See [VSCode Packaging](./vscode-extension/packaging.md) for details. + +### Zed Extensions + +The Zed extension (`zed-extension/`) points to GitHub release archives. Publishing requires submitting a PR to the [zed-industries/extensions](https://github.com/zed-industries/extensions) repository. + +### Direct Download + +Binary archives are attached to each GitHub release for direct download: +- `symposium-darwin-arm64.tar.gz` +- `symposium-darwin-x64.tar.gz` +- `symposium-linux-x64.tar.gz` +- `symposium-linux-arm64.tar.gz` +- `symposium-linux-x64-musl.tar.gz` +- `symposium-windows-x64.zip` + +## Supported Platforms + +| Platform | Architecture | Notes | +|----------|--------------|-------| +| macOS | arm64 (Apple Silicon) | Primary development platform | +| macOS | x64 (Intel) | | +| Linux | x64 (glibc) | Standard Linux distributions | +| Linux | arm64 | ARM servers, Raspberry Pi | +| Linux | x64 (musl) | Static binary, Alpine Linux | +| Windows | x64 | | + +## Secrets Required + +The release workflow requires these GitHub secrets: + +| Secret | Purpose | +|--------|---------| +| `RELEASE_PLZ_TOKEN` | GitHub token for release-plz to create releases | +| `VSCE_PAT` | Azure DevOps PAT for VSCode Marketplace | +| `OVSX_PAT` | Open VSX access token | diff --git a/md/design/vscode-extension/packaging.md b/md/design/vscode-extension/packaging.md new file mode 100644 index 00000000..25b70c66 --- /dev/null +++ b/md/design/vscode-extension/packaging.md @@ -0,0 +1,94 @@ +# Extension Packaging + +This chapter documents the design decisions for building and distributing the VSCode extension. + +## Architecture Overview + +The extension consists of two parts that must be bundled together: + +1. **TypeScript code** - The extension logic and webview, bundled via webpack +2. **Native binary** - The `symposium-acp-agent` Rust binary for the target platform + +## Platform-Specific Extensions + +We publish **separate extensions for each platform** rather than a universal extension containing all binaries. + +**Rationale:** +- A universal extension would be ~70MB+ (all platform binaries) +- Platform-specific extensions are ~7MB each +- VSCode Marketplace natively supports this - users automatically get the right variant +- Aligns with how other extensions with native dependencies work (rust-analyzer, etc.) + +**Supported platforms:** + +| Platform | Description | +|----------|-------------| +| darwin-arm64 | macOS Apple Silicon | +| darwin-x64 | macOS Intel | +| linux-x64 | Linux x86_64 | +| linux-arm64 | Linux ARM64 | +| win32-x64 | Windows x86_64 | + +## Binary Resolution + +The extension uses a fallback chain for finding the conductor binary: + +1. **Bundled binary** in `bin//` (production) +2. **PATH lookup** (development) + +This enables local development without packaging - developers can `cargo install` the binary and the extension finds it in PATH. + +## Release Flow + +Releases are orchestrated through release-plz and GitHub Actions: + +``` +release-plz creates tag + ↓ +GitHub Release created + ↓ +Binary build workflow triggered + ↓ +┌───────────────────────────────────────┐ +│ Build binaries (parallel) │ +│ - macOS arm64/x64 │ +│ - Linux x64/arm64/musl │ +│ - Windows x64 │ +└───────────────────────────────────────┘ + ↓ +Upload archives to GitHub Release + ↓ +┌───────────────────────────────────────┐ +│ Build VSCode extensions (parallel) │ +│ - One per platform │ +│ - Each bundles its platform binary │ +└───────────────────────────────────────┘ + ↓ +Upload .vsix files to GitHub Release + ↓ +Publish to marketplaces (TODO) +``` + +**Why GitHub Releases as the source:** +- Single source of truth for all binaries +- Enables Zed extension (points to release archives) +- Enables direct downloads for users not on VSCode +- Versioned and immutable + +## Vendored mynah-ui + +The extension depends on a fork of mynah-ui (AWS's chat UI component) located in `vendor/mynah-ui`. This is managed as a git subtree. + +**Why vendor:** +- Enables custom features not yet upstream +- Webpack bundles it into `webview.js` - only the built output ships in the extension + +## Local Development + +For development without building platform packages: + +1. Install the conductor: `cargo install --path src/symposium-acp-agent` +2. Build the extension: `cd vscode-extension && npm run compile` +3. Launch via F5 in VSCode + +The extension finds the binary in PATH when no bundled binary exists. diff --git a/vendor/mynah-ui/.eslintignore b/vendor/mynah-ui/.eslintignore new file mode 100644 index 00000000..2b68ecc0 --- /dev/null +++ b/vendor/mynah-ui/.eslintignore @@ -0,0 +1,25 @@ +*.js +*.json +*.md +*.css +*.ttf +*.scss +*.svg +*.png +*.map +*.html +*.xml +*.zip +.github +.husky +example +api-docs +dist +docs +out +LICENSE +THIRD-PARTY-LICENSES +NOTICE +Dockerfile +e2e-results +coverage/ diff --git a/vendor/mynah-ui/.eslintrc.js b/vendor/mynah-ui/.eslintrc.js new file mode 100644 index 00000000..9c4e0f21 --- /dev/null +++ b/vendor/mynah-ui/.eslintrc.js @@ -0,0 +1,37 @@ +module.exports = { + env: { + browser: true, + es2021: true, + node: true, + }, + extends: ['standard-with-typescript'], + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: __dirname, + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './ui-tests/tsconfig.json'], + }, + plugins: ['@typescript-eslint', 'prettier'], + rules: { + 'no-case-declarations': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/semi': [2, 'always'], + 'comma-dangle': [2, 'only-multiline'], + 'array-bracket-spacing': [2, 'always'], + 'no-useless-call': 'off', + '@typescript-eslint/member-delimiter-style': [ + 'error', + { + multiline: { + delimiter: 'semi', + requireLast: true, + }, + singleline: { + delimiter: 'semi', + requireLast: false, + }, + }, + ], + }, +}; diff --git a/vendor/mynah-ui/.github/CODEOWNERS b/vendor/mynah-ui/.github/CODEOWNERS new file mode 100644 index 00000000..f4616b6c --- /dev/null +++ b/vendor/mynah-ui/.github/CODEOWNERS @@ -0,0 +1 @@ +* @aws/flare diff --git a/vendor/mynah-ui/.github/ISSUE_TEMPLATE/bug_report.md b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..f1a3424d --- /dev/null +++ b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Help us improve MynahUI by reporting a bug +labels: bug +--- + +## Problem + +## Steps to reproduce the issue + + + +## Expected behavior + +## System details + +- OS: +- Tested enviroment: + - [ ] Web (Demo app) + - [ ] VSCode Amazon Q Chat + - [ ] JetBrains IntelliJ Amazon Q Chat +- Enviroment extension version (if applicable): +- [ ] Enviroment is remote diff --git a/vendor/mynah-ui/.github/ISSUE_TEMPLATE/feature_request.md b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..52ea5848 --- /dev/null +++ b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,9 @@ +--- +name: Feature request +about: Suggest an idea for MynahUI +labels: new feature +--- + +## Problem + +## Expected behavior diff --git a/vendor/mynah-ui/.github/ISSUE_TEMPLATE/guidance_request.md b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/guidance_request.md new file mode 100644 index 00000000..2d19608b --- /dev/null +++ b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/guidance_request.md @@ -0,0 +1,17 @@ +--- +name: Ask a question +about: Ask for guidance, "how to", or other questions +labels: question +--- + +## System details + +- OS: +- Enviroment to run: + - [ ] Web + - [ ] VSCode Amazon Q Chat + - [ ] JetBrains IntelliJ Amazon Q Chat + +## Question + + diff --git a/vendor/mynah-ui/.github/ISSUE_TEMPLATE/improvement.md b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/improvement.md new file mode 100644 index 00000000..34af7a0c --- /dev/null +++ b/vendor/mynah-ui/.github/ISSUE_TEMPLATE/improvement.md @@ -0,0 +1,14 @@ +--- +name: Improvement +about: Improve a currently existing feature +labels: improvement +--- + +## Current state + + +## Improved behavior + + +## Why? + \ No newline at end of file diff --git a/vendor/mynah-ui/.github/PULL_REQUEST_TEMPLATE.md b/vendor/mynah-ui/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..ae143d6f --- /dev/null +++ b/vendor/mynah-ui/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,20 @@ +## Problem + +## Solution + + + +## Tests +- [ ] I have tested this change on VSCode +- [ ] I have tested this change on JetBrains +- [ ] I have added/updated the documentation (if applicable) + +## License + +By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/vendor/mynah-ui/.github/workflows/deploy.yml b/vendor/mynah-ui/.github/workflows/deploy.yml new file mode 100644 index 00000000..dd7aaa3f --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/deploy.yml @@ -0,0 +1,60 @@ +# Sample workflow for building and deploying a Jekyll site to GitHub Pages +name: Deploy to GitHub Pages + +on: + workflow_dispatch: + push: + tags: ['v*.*'] + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Build job + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: '24.x' + registry-url: 'https://registry.npmjs.org' + scope: '@aws' + - run: npm ci + - name: Build mynah-ui + run: npm run build + - name: Build demo app + working-directory: ./example + run: npm run pack + - name: Create API doc + run: npm run api-doc-deploy + - name: Setup Pages + uses: actions/configure-pages@v4 + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + with: + source: ./example/dist + destination: ./_site + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + + # Deployment job + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/vendor/mynah-ui/.github/workflows/e2e-linux.yml b/vendor/mynah-ui/.github/workflows/e2e-linux.yml new file mode 100644 index 00000000..1e58041e --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/e2e-linux.yml @@ -0,0 +1,76 @@ +name: Run E2E Tests (Linux) +on: workflow_call +jobs: + e2e-linux: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + # Checkout the PR branch + - name: Checkout Code + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + + # Set up Docker Buildx + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Login to GitHub Container Registry + - name: Login to GitHub Container Registry + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Build Docker image with caching (for non-fork PRs) + - name: Build E2E tests Docker Image (with cache) + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + uses: docker/build-push-action@v5 + with: + context: . + push: false + load: true + tags: mynah-ui-e2e:latest + cache-from: type=registry,ref=ghcr.io/${{ github.repository }}/mynah-ui-e2e:buildcache + cache-to: type=registry,ref=ghcr.io/${{ github.repository }}/mynah-ui-e2e:buildcache,mode=max + + # Build Docker image without caching (for fork PRs) + - name: Build E2E tests Docker Image (no cache) + if: ${{ github.event.pull_request.head.repo.full_name != github.repository }} + uses: docker/build-push-action@v5 + with: + context: . + push: false + load: true + tags: mynah-ui-e2e:latest + + # Run the Docker container + - name: Run E2E tests Docker Container + run: npm run docker:run + env: + WEBKIT_FORCE_COMPLEX_TEXT: 0 + WEBKIT_DISABLE_COMPOSITING_MODE: 1 + PLAYWRIGHT_BROWSERS_PATH: 0 + + # Extract test results from Docker container + - name: Extract test results from Docker container + if: always() + run: npm run docker:extract + + # Upload test results as artifact + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results + path: ./ui-tests/__results__ + retention-days: 30 + report: + needs: e2e-linux + if: always() + uses: ./.github/workflows/test-report.yml diff --git a/vendor/mynah-ui/.github/workflows/lint.yml b/vendor/mynah-ui/.github/workflows/lint.yml new file mode 100644 index 00000000..d840fc39 --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/lint.yml @@ -0,0 +1,26 @@ +name: Run linter +on: workflow_call + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Set up Node + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '24.x' + registry-url: 'https://registry.npmjs.org' + scope: '@aws' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build && cd ./ui-tests && npm install && cd .. + + - name: Run linter + run: npm run lint \ No newline at end of file diff --git a/vendor/mynah-ui/.github/workflows/main-push.yml b/vendor/mynah-ui/.github/workflows/main-push.yml new file mode 100644 index 00000000..d3c504ea --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/main-push.yml @@ -0,0 +1,12 @@ +name: Main Branch Push +on: + push: + branches: + - main +jobs: + lint: + uses: ./.github/workflows/lint.yml + unit-tests: + uses: ./.github/workflows/unit-tests.yml + e2e-linux: + uses: ./.github/workflows/e2e-linux.yml diff --git a/vendor/mynah-ui/.github/workflows/new_pr.yml b/vendor/mynah-ui/.github/workflows/new_pr.yml new file mode 100644 index 00000000..9790b1b5 --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/new_pr.yml @@ -0,0 +1,23 @@ +name: New PR created +on: + pull_request: + types: [opened, reopened, ready_for_review, synchronize] +jobs: + notify: + if: github.event.pull_request.draft == false + name: Slack notification + runs-on: ubuntu-latest + steps: + - name: Inform channels + uses: fjogeleit/http-request-action@v1 + with: + url: '${{ secrets.SLACK_WEBHOOK_URL }}' + method: 'POST' + customHeaders: '{"Content-Type": "application/json"}' + data: '{ "title": "${{ github.event.pull_request.title }}", "author": "${{ github.event.pull_request.user.login }}", "link": "${{ github.event.pull_request.html_url }}"}' + lint: + uses: ./.github/workflows/lint.yml + unit-tests: + uses: ./.github/workflows/unit-tests.yml + e2e-linux: + uses: ./.github/workflows/e2e-linux.yml \ No newline at end of file diff --git a/vendor/mynah-ui/.github/workflows/publish.yml b/vendor/mynah-ui/.github/workflows/publish.yml new file mode 100644 index 00000000..801aa213 --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/publish.yml @@ -0,0 +1,74 @@ +name: Publish package to NPM +on: + workflow_dispatch: + push: + tags: ['v*.*', 'beta*.*'] + +permissions: + id-token: write # Required for OIDC authentication with npm + packages: write + checks: write + contents: write + actions: read + +jobs: + lint: + uses: ./.github/workflows/lint.yml + unit-tests: + uses: ./.github/workflows/unit-tests.yml + e2e-linux: + uses: ./.github/workflows/e2e-linux.yml + build-beta: + if: ${{ startsWith(github.ref_name, 'beta') }} + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '24.x' + registry-url: 'https://registry.npmjs.org' + scope: '@aws' + - name: Beta release (build and publish) + run: | + echo "Publishing beta release" + npm ci + npm run build + npm publish --tag beta --access public + build-prod: + if: ${{ startsWith(github.ref_name, 'v') }} + needs: [lint, unit-tests, e2e-linux] + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '24.x' + registry-url: 'https://registry.npmjs.org' + scope: '@aws' + - name: Install dependencies and build + run: npm ci && npm run build + - name: Build demo app + run: npm run packdemo + - name: Get release info + id: get_release + uses: bruceadams/get-release@v1.3.2 + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Upload demo app to assets + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.get_release.outputs.upload_url }} + asset_path: ./example/mynah-ui-demo.zip + asset_name: mynah-ui-demo.zip + asset_content_type: application/zip + - name: Publish to npm + run: npm publish --access public + - name: Update Channels + uses: fjogeleit/http-request-action@v1 + with: + url: '${{ secrets.SLACK_ENDPOINT }}' + method: 'POST' + customHeaders: '{"Content-Type": "application/json"}' + data: '{"version": "${{ steps.get_release.outputs.tag_name }}", "url": "${{ steps.get_release.outputs.html_url }}"}' \ No newline at end of file diff --git a/vendor/mynah-ui/.github/workflows/test-report.yml b/vendor/mynah-ui/.github/workflows/test-report.yml new file mode 100644 index 00000000..c385fcb5 --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/test-report.yml @@ -0,0 +1,20 @@ +name: 'Test Report' +on: workflow_call +permissions: + contents: read + actions: read + checks: write +jobs: + report: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: dorny/test-reporter@v2.0.0 + with: + name: E2E Tests Report + artifact: test-results + path: ./__reports__/junit.xml + reporter: jest-junit + fail-on-error: false \ No newline at end of file diff --git a/vendor/mynah-ui/.github/workflows/unit-tests.yml b/vendor/mynah-ui/.github/workflows/unit-tests.yml new file mode 100644 index 00000000..6819f8cc --- /dev/null +++ b/vendor/mynah-ui/.github/workflows/unit-tests.yml @@ -0,0 +1,31 @@ +name: Run Unit tests +on: workflow_call +jobs: + unit-tests: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + # Set up Node + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: '24.x' + registry-url: 'https://registry.npmjs.org' + scope: '@aws' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build + + - name: Run Unit tests + run: npm run tests:unit + + - name: Upload coverage reports + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: ./coverage \ No newline at end of file diff --git a/vendor/mynah-ui/.gitignore b/vendor/mynah-ui/.gitignore new file mode 100644 index 00000000..0f5b4729 --- /dev/null +++ b/vendor/mynah-ui/.gitignore @@ -0,0 +1,15 @@ +out +dist +**api-docs +build +node_modules +*.bk +*.zip +**/.DS_Store +.idea +package-lock.json +.gitcommit +.vscode +__results__/ +e2e-results/ +coverage/ \ No newline at end of file diff --git a/vendor/mynah-ui/.husky/pre-push b/vendor/mynah-ui/.husky/pre-push new file mode 100644 index 00000000..5ec09714 --- /dev/null +++ b/vendor/mynah-ui/.husky/pre-push @@ -0,0 +1,2 @@ +npm run lint +npm run format:check diff --git a/vendor/mynah-ui/.npmignore b/vendor/mynah-ui/.npmignore new file mode 100644 index 00000000..9a646bb2 --- /dev/null +++ b/vendor/mynah-ui/.npmignore @@ -0,0 +1,19 @@ +out +**/node_modules +*.bk +**/.DS_Store +.idea +.gitcommit +.github +.eslintignore +.gitignore +.npmignore +.eslintrc.json +package-lock.json +tsconfig.json +webpack.config.js +**/src +api-docs +**/__test__ +example +example-react \ No newline at end of file diff --git a/vendor/mynah-ui/.prettierignore b/vendor/mynah-ui/.prettierignore new file mode 100644 index 00000000..7c928e9c --- /dev/null +++ b/vendor/mynah-ui/.prettierignore @@ -0,0 +1,9 @@ +*.*ts +*.md +package-lock.json +.github +.husky +api-docs +docs +dist +build \ No newline at end of file diff --git a/vendor/mynah-ui/.prettierrc b/vendor/mynah-ui/.prettierrc new file mode 100644 index 00000000..36ce1ae2 --- /dev/null +++ b/vendor/mynah-ui/.prettierrc @@ -0,0 +1,8 @@ +{ + "printWidth": 120, + "tabWidth": 4, + "singleQuote": true, + "semi": true, + "bracketSpacing": true, + "endOfLine": "lf" +} diff --git a/vendor/mynah-ui/CODE_OF_CONDUCT.md b/vendor/mynah-ui/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..5b627cfa --- /dev/null +++ b/vendor/mynah-ui/CODE_OF_CONDUCT.md @@ -0,0 +1,4 @@ +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. diff --git a/vendor/mynah-ui/CONTRIBUTING.md b/vendor/mynah-ui/CONTRIBUTING.md new file mode 100644 index 00000000..3eb4d0eb --- /dev/null +++ b/vendor/mynah-ui/CONTRIBUTING.md @@ -0,0 +1,62 @@ +# Contributing Guidelines + +Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional +documentation, we greatly value feedback and contributions from our community. + +Please read through this document before submitting any issues or pull requests to ensure we have all the necessary +information to effectively respond to your bug report or contribution. + + +## Reporting Bugs/Feature Requests + +We welcome you to use the GitHub issue tracker to report bugs or suggest features. + +When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already +reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: + +* A reproducible test case or series of steps +* The version of our code being used +* Any modifications you've made relevant to the bug +* Anything unusual about your environment or deployment + + +## Contributing via Pull Requests + +**Read [DEVELOPER Guidelines](./docs/DEVELOPER.md) first.** + +Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: + +1. You are working against the latest source on the *main* branch. +2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. +3. You open an issue to discuss any significant work - we would hate for your time to be wasted. + +To send us a pull request, please: + +1. Fork the repository. +2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. +3. Ensure local tests pass. +4. Commit to your fork using clear commit messages. +5. Send us a pull request, answering any default questions in the pull request interface. +6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. + +GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and +[creating a pull request](https://help.github.com/articles/creating-a-pull-request/). + + +## Finding contributions to work on +Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. + + +## Code of Conduct +This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). +For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact +opensource-codeofconduct@amazon.com with any additional questions or comments. + + +## Security issue notifications +If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. + + +## Licensing + +See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. diff --git a/vendor/mynah-ui/DEVELOPMENT.md b/vendor/mynah-ui/DEVELOPMENT.md new file mode 100644 index 00000000..93307d65 --- /dev/null +++ b/vendor/mynah-ui/DEVELOPMENT.md @@ -0,0 +1,24 @@ +# Mynah UI +This package is the whole UI of AWS Codewhisperer Chat extension UI for Web, VSCode and Intellij IDEs written in typescript without any framework or third-party UI library dependency. Purpose of the separated UI is to handle the interactions and look & feel of the UI from one single source. + +## How to release +### Production +You need to create a new release from your desired branch with a tag which should follow the naming `v*.*` to release a production version to npm. The tag you're creating shouldn't be existed before. + +### Beta releases +If you need to release a beta version first you need to specify the version name inside `package.json` which should follow the versioning `2.0.0-beta.1` +After that you need to create a new release from your desired branch with a tag which should follow the naming `beta*.*` to release a beta version to npm. The tag you're creating shouldn't be existed before. + +``` console +please see publish.yml and beta.yml for releasing details. +``` + + +## Security + +See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + +## License + +This project is licensed under the Apache-2.0 License. + diff --git a/vendor/mynah-ui/Dockerfile b/vendor/mynah-ui/Dockerfile new file mode 100644 index 00000000..d366d271 --- /dev/null +++ b/vendor/mynah-ui/Dockerfile @@ -0,0 +1,51 @@ +# Version-agnostic Dockerfile for Mynah UI E2E Tests +# Supports dynamic Playwright version detection +ARG PLAYWRIGHT_VERSION=latest +FROM mcr.microsoft.com/playwright:${PLAYWRIGHT_VERSION} + +# Set working directory +WORKDIR /app + +# Copy the src from the root +COPY ./src /app/src + +# Copy config files from root +COPY ./package.json /app +COPY ./package-lock.json /app +COPY ./postinstall.js /app +COPY ./webpack.config.js /app +COPY ./tsconfig.json /app + +# Copy scripts directory for version-agnostic setup +COPY ./scripts /app/scripts + +# Copy required files from ui-tests +COPY ./ui-tests/package.json /app/ui-tests/ +COPY ./ui-tests/playwright.config.ts /app/ui-tests/ +COPY ./ui-tests/tsconfig.json /app/ui-tests/ +COPY ./ui-tests/webpack.config.js /app/ui-tests/ + +# Copy the directories from ui-tests +COPY ./ui-tests/__test__ /app/ui-tests/__test__ +COPY ./ui-tests/src /app/ui-tests/src +COPY ./ui-tests/__snapshots__ /app/ui-tests/__snapshots__ + +# Install dependencies and build MynahUI +RUN npm install +RUN npm run build + +# Setup Playwright with version-agnostic approach +RUN cd ./ui-tests && node ../scripts/setup-playwright.js && npm run prepare + +# Ensure all browsers are installed with dependencies +RUN cd ./ui-tests && npx playwright install --with-deps + +# Run health check to verify installation +RUN cd ./ui-tests && node ../scripts/docker-health-check.js + +# Set environment variables for WebKit +ENV WEBKIT_FORCE_COMPLEX_TEXT=0 +ENV WEBKIT_DISABLE_COMPOSITING_MODE=1 + +# Default command to run the tests +CMD ["sh", "-c", "cd ./ui-tests && npm run e2e${BROWSER:+:$BROWSER}"] diff --git a/vendor/mynah-ui/LICENSE b/vendor/mynah-ui/LICENSE new file mode 100644 index 00000000..67db8588 --- /dev/null +++ b/vendor/mynah-ui/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/vendor/mynah-ui/NOTICE b/vendor/mynah-ui/NOTICE new file mode 100644 index 00000000..616fc588 --- /dev/null +++ b/vendor/mynah-ui/NOTICE @@ -0,0 +1 @@ +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. diff --git a/vendor/mynah-ui/README.md b/vendor/mynah-ui/README.md new file mode 100644 index 00000000..fd8c9ab2 --- /dev/null +++ b/vendor/mynah-ui/README.md @@ -0,0 +1,69 @@ + +# Mynah UI +> *A Data & Event Driven Chat Interface Library for Browsers and Webviews* + +[![PR](https://github.com/aws/mynah-ui/actions/workflows/new_pr.yml/badge.svg?branch=main)](https://github.com/aws/mynah-ui/actions/workflows/new_pr.yml) +[![Beta](https://github.com/aws/mynah-ui/actions/workflows/beta.yml/badge.svg?branch=main)](https://github.com/aws/mynah-ui/actions/workflows/beta.yml) +[![Publish](https://github.com/aws/mynah-ui/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/aws/mynah-ui/actions/workflows/publish.yml) +[![Deploy](https://github.com/aws/mynah-ui/actions/workflows/deploy.yml/badge.svg?branch=main)](https://github.com/aws/mynah-ui/actions/workflows/deploy.yml) + +**Mynah UI** is a **_data and event_** driven chat interface designed for browsers and webviews on IDEs or any platform supporting the latest web technologies. It is utilized by Amazon Q for [VSCode](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-toolkit-vscode), [JetBrains](https://plugins.jetbrains.com/plugin/11349-aws-toolkit--amazon-q-codewhisperer-and-more), [Visual studio](https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.AWSToolkitforVisualStudio2022&ssr=false#overview) and [Eclipse](https://marketplace.eclipse.org/content/amazon-q). + +Mynah UI operates independently of any framework or UI library, enabling seamless integration into any web-based project. This design choice ensures high configurability for theming, supporting various use cases. It functions as a standalone solution, requiring only a designated rendering location within the DOMTree. + +## Table of contents +- [Quick links](#quick-links) +- [Setup, configuration and use](#setup-configuration-and-use) + - [Guides and documentation](#guides-and-documentation) + - [Preview](#preview) +- [Supported Browsers](#supported-browsers) +- [Security](#security) +- [License](#license) + +### Quick links +* [Live Demo](https://aws.github.io/mynah-ui/) +* [API Docs](https://aws.github.io/mynah-ui/api-doc/index.html) + + +### Setup, configuration and use + +To set up your local development environment quickly, run the following command: + +```bash +npm run dev +``` + +This command will: +1. **Clean**: Remove existing `dist` and `node_modules` directories to ensure you're working with a fresh environment. +2. **Install**: Reinstall all necessary dependencies for both the main project and the example project. +3. **Build**: Compile the project using Webpack in production mode. +4. **Start Example**: Install dependencies and build the example project, then start the development server with `watch` mode enabled. The project will be served on `localhost:9000` using `live-server`. +5. **Watch**: Start the main project in `watch` mode. +After running this command, any changes you make will automatically rebuild and refresh your development environment, allowing you to work seamlessly. + + +#### Guides and documentation +Please refer to the following guides: + +* [Startup guide](./docs/STARTUP.md) +* [Constructor properties](./docs/PROPERTIES.md) +* [Configuration](./docs/CONFIG.md) +* [Data model](./docs/DATAMODEL.md) +* [Usage](./docs/USAGE.md) +* [Styling](./docs/STYLING.md) +* [Testing](./docs/TESTING.md) +* [Developer guidelines (contribution)](./docs/DEVELOPER.md) + +#### Preview +![Preview](./docs/img/splash.gif) + +### Supported Browsers + +**Mynah UI** - due to its extensive CSS structure - supports only evergreen browsers, including WebKit-based WebUI renderers. + +## Security + +See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information. + +# License +[Apache 2.0 License.](LICENSE) diff --git a/vendor/mynah-ui/THIRD-PARTY-LICENSES b/vendor/mynah-ui/THIRD-PARTY-LICENSES new file mode 100644 index 00000000..02b902d0 --- /dev/null +++ b/vendor/mynah-ui/THIRD-PARTY-LICENSES @@ -0,0 +1,142 @@ +** highlight.js; version 11.11.0 -- https://github.com/highlightjs/highlight.js/ + +BSD 3-Clause License + +Copyright (c) 2006, Ivan Sagalaev. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------ + +** marked; version 7.0.3 -- https://github.com/markedjs/marked/ + +MIT LICENSE + +Copyright (c) 2011-2022 Christopher Jeffrey. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------ + +** escape-html; version 1.0.3 -- https://github.com/component/escape-html/ + +MIT License + +Copyright (c) 2012-2013 TJ Holowaychuk +Copyright (c) 2015 Andreas Lubbe +Copyright (c) 2015 Tiancheng "Timothy" Gu + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +------ + +** unescape-html; version 1.1.0 -- https://github.com/ForbesLindesay/unescape-html + +MIT LICENSE + +Copyright (c) 2013 Forbes Lindesay + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +------ + +** sanitize-html; version 2.12.1 -- https://github.com/apostrophecms/sanitize-html + +MIT LICENSE + +Copyright (c) 2013, 2014, 2015 P'unk Avenue LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + + +------ + +** just-clone; version 6.2.0 -- https://github.com/angus-c/just + +The MIT License (MIT) + +Copyright (c) 2016-2023 Angus Croll + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/mynah-ui/docs/ARCHITECTURE.md b/vendor/mynah-ui/docs/ARCHITECTURE.md new file mode 100644 index 00000000..e13e2f16 --- /dev/null +++ b/vendor/mynah-ui/docs/ARCHITECTURE.md @@ -0,0 +1,91 @@ +# MynahUI Architecture + +## How do the Consumer and MynahUI work together? + +Before how it works, it is better to clarify how to make it work. To consume MynahUI, there is only one single requirement, `npm` access. Adding `@aws/mynah-ui` to the `package.json` dependencies will allow the consumer to grab the desired version of MynahUI and allow them to create an instance of it to be rendered in the desired dom element. + +To install: + +``` +npm install @aws/mynah-ui +``` + +And to create the instance: + +``` +const mynahUI = new MynahUI({...}); +``` + +#### So, how is the flow between the consumer and the MynahUI in general? + +As indicated above in the section, it expects data and sends events. The expected data from the MynahUI can be passed with several ways like defining them during the initialization, updating the data store directly or adding one or more chat items during the runtime. Let’s take a look to the basic flow between MynahUI and the consumer: + +![image](https://github.com/user-attachments/assets/052ff1a4-e2f8-449f-a793-32dff333f6a5) + +As we can clarify from the flow, MynahUI expects data from the consumer app and renders new elements or updates existing ones on the UI. And it is also responsible to deliver the user events to the consumer app to let them run their logic. + + + + +## How does MynahUI work under the hood? +![image](https://github.com/user-attachments/assets/f9ea537f-6db7-4249-b347-f46812646e7e) + +MynahUI relies on three core structures, the **Data Store**, **Global Event Handler** and **Dom Builder**. The combination of these 3 basically drives the MynahUI structure. + + +#### Let’s break down the **Data Store**: + +The data store consists of 2 parts. The main data store holds all the current data for the tabs. Since MynahUI supports multiple tabs, each tab has its own data. And the second block in the data store is the data for each tab. + +Global Data Store → Tab Data Store 1, Tab Data Store 2 ... Tab Data Store N + +Tab Data store holds every single content related with that tab, like chat items, tab title, background, prompt input field related information etc. + +Here’s an overview of what it looks like: + +![image](https://github.com/user-attachments/assets/f375031a-e2bb-4015-a3c1-ae88739b59cd) + + + +#### Let’s break down the Global Event Handler: + +The global event handler can be used by any component, to listen or fire a non data related event happened through the system. And more importantly, they can also fire events to inform the subscribers. +For example, when a tab gets focus (basically being selected) it fires an event through the global event system which is called `TAB_FOCUS`. And if there is any subscriber to that event, their attached handler function will be called. + +![image](https://github.com/user-attachments/assets/ea9157da-0030-4d85-8ede-4cbe918d6512) + + +#### Let’s break down the DomBuilder: + +DomBuilder is at the heart of the rendering part of MynahUI. Basically, every single UI (HTML) element is being generated from the DomBuilder. It helps to manage dom manipulation from one single space. For example when you need to add some specific attribute to any dom generated in the MynahUI, it will be handled from this **singleton** class. + +![image](https://github.com/user-attachments/assets/40ccab42-a64f-4120-95a1-57822add9f80) + + + +The main class (MynahUI) handles all the creation of these core parts and the communication between them. + +To clarify how all those structures work together, **a simplified flow can be showed as follows**: + +![image](https://github.com/user-attachments/assets/f816ad36-4ad3-4e13-913e-d6afb9939a4f) + + + +### How do components work? + +Components are using the DomBuilder to build up their HTML elements. Or, they can also use other components as well. +Each component should have their `render`, which should also be just an HTMLElement or an ExtendedHTMLElement which is the output of the DomBuilder. +For the styling of the elements and components, MynahUI uses basic css structure. However to make it as clean as possible to read and generate proper hierarchies, we’re building the output css from SCSS. + + +>But an important notice here, we’re trying to avoid using SCSS variables as much as possible and keep every possible thing as a CSS Custom property. + + +The styling of the components cannot have static values or inline values. With the support of the CSS custom properties, it is possible to theme it in every single detail like colors, paddings, sizings, fonts even animations and transitions. + +**Here’s a general look of a component structure:** + +![image](https://github.com/user-attachments/assets/d7de7181-2e0d-43c2-8118-fffa7cc36156) + + + diff --git a/vendor/mynah-ui/docs/CONFIG.md b/vendor/mynah-ui/docs/CONFIG.md new file mode 100644 index 00000000..dea7d04a --- /dev/null +++ b/vendor/mynah-ui/docs/CONFIG.md @@ -0,0 +1,417 @@ +# MynahUI Config + +You can set the config from the constructor parameters while creating a new instance of `mynah-ui`. + +_**Note:** You cannot set it on runtime. It is getting used just once during the initialization._ + +```typescript +... +interface ConfigModel { + // Do not forget that you have to provide all of them + // Config allows partial set of texts + texts: { + mainTitle?: string; + feedbackFormTitle?: string; + feedbackFormDescription?: string; + feedbackFormOptionsLabel?: string; + feedbackFormCommentLabel?: string; + feedbackThanks?: string; + feedbackReportButtonLabel?: string; + codeSuggestions?: string; + files?: string; + insertAtCursorLabel?: string; + copy?: string; + showMore?: string; + save?: string; + cancel?: string; + submit?: string; + pleaseSelect?: string; + stopGenerating?: string; + copyToClipboard?: string; + noMoreTabsTooltip?: string; + codeSuggestionWithReferenceTitle?: string; + spinnerText?: string; + tabCloseConfirmationMessage?: string; + tabCloseConfirmationKeepButton?: string; + tabCloseConfirmationCloseButton?: string; + noTabsOpen: string; // Supports markdown + openNewTab: string; + commandConfirmation: string; + pinContextHint: string; + dragOverlayText: string; + }; + // Options to show up on the overlay feedback form + // after user clicks to downvote on a chat item + // and clicks 'Report' again + feedbackOptions: Array<{ + label: string; + value: string; + }>; + tabBarButtons?: TabBarMainAction[]; // Tab bar buttons will be shown on the right of the tab + maxUserInput: number; // max number of chars for the input field + userInputLengthWarningThreshold: number; // The amount of characters in the input field necessary for the character limit warning to show + codeInsertToCursorEnabled?: boolean; // show or hide copy buttons on code blocks system wide + codeCopyToClipboardEnabled?: boolean; // show or hide insert to cursor buttons on code blocks system wide + autoFocus: boolean; // auto focuses to input panel after every action + maxTabs: number; // set 1 to hide tabs panel + showPromptField: boolean; // shows prompt field (default: true) + dragOverlayIcon?: MynahIcons | MynahIconsType | CustomIcon; // icon displayed in the overlay when a file is dragged into the chat area + enableSearchKeyboardShortcut?: boolean; // if true, calls onSearchShortcut on Command + f or Ctrl + f (default: false) +} +... +``` +--- + +


+ + +# `tabBarButtons` + +You can put buttons on the right of the tab bar also with some inner buttons inside a menu. You can do it in two different ways. If you want the buttons globally available for every tab you can use the `tabBarButtons` in the config. If you want them set individually for different tabs check the **[DATAMODEL Documentation](./DATAMODEL.md#tabbarbuttons)**. + +```typescript +const mynahUI = new MynahUI({ + ... + config: { + ... + tabBarButtons: [ + { + id: 'clear', + description: 'Clear messages in this tab', + icon: MynahIcons.REFRESH, + }, + { + id: 'multi', + icon: MynahIcons.ELLIPSIS, + items: [ + { + id: 'menu-action-1', + text: 'Menu action 1!', + icon: MynahIcons.CHAT, + }, + { + id: 'menu-action-2', + text: 'Menu action 2!', + icon: MynahIcons.CODE_BLOCK, + }, + { + id: 'menu-action-3', + text: 'Menu action 3!' + } + ] + } + ] + } + ... +}); +``` + +

+ mainTitle +
+ mainTitle +

+ +--- + + + +# `texts` +All static texts will be shown on UI. +Please take a look at each image to identify which text belongs to which item on UI. + +## mainTitle +Default tab title text if it is not set through store data for that tab. + +

+ mainTitle +

+ +--- + +## feedbackFormTitle, feedbackFormDescription, feedbackFormOptionsLabel, feedbackFormCommentLabel, submit, cancel +

+ feedbackForm +

+ + +--- + +## fileTreeTitle, rootFolderTitle, feedbackFormCommentLabel, submit, cancel +

+ fileTree +

+ + +--- + +## pleaseSelect +

+ feedbackForm +

+ +--- + +## feedbackThanks, feedbackReportButtonLabel, showMore +

+ voteAndSourceActions +

+ +--- + +## stopGenerating +

+ stopGenerating +

+ +--- + +## insertAtCursorLabel, copy +

+ copyInsertToCursor +

+ +--- + +## codeSuggestions, files, codeSuggestionWithReferenceTitle +

+ codeFileSuggestions +

+ +--- + +## spinnerText +

+ spinnerText +

+ +--- + +## tabCloseConfirmationMessage, tabCloseConfirmationKeepButton, tabCloseConfirmationCloseButton +

+ tabCloseConfirmation +

+ +--- + +## noMoreTabsTooltip +

+ noMoreTabsTooltip +

+ +--- + +## noTabsOpen, openNewTab +

+ noTabsOpen +

+ +--- + +## commandConfirmation +

+ commandConfirmation +

+ +## pinContextHint +

+ pinContextHint +

+--- + +## dragOverlayText +

+ dragOverlayText +

+--- + +


+ +# `feedbackOptions` + +Feedback type options to be shown on feedback form. +defaults: +```typescript +... +feedbackOptions: [ + { + value: 'inaccurate-response', + label: 'Inaccurate response', + }, + { + value: 'harmful-content', + label: 'Harmful content' + }, + { + value: 'overlap', + label: 'Overlaps with existing content' + }, + { + value: 'incorrect-syntax', + label: 'Incorrect syntax' + }, + { + value: 'buggy-code', + label: 'Buggy code' + }, + { + value: 'low-quality', + label: 'Low quality' + }, + { + value: 'other', + label: 'Other' + } + ], +... +``` + +

+ feedbackOptions +

+ +--- + +


+ +# `maxTabs` +Maximum number of tabs user/system can open in a single instance of `mynah-ui`. + +default: `1000` + +An important note here is that if you provide **`1`** to maxTabs, it will not show the tab bar at all. However you still need to add a tab then initially to show a content. + +And finally, if you try to add tabs more than given `maxTabs` amount while initializing the MynahUI with [Constructor Properties](./PROPERTIES.md), it will only generate the tabs till it reaches the `maxTabs` limit. + +_Assume that you've provided `1` for `maxTabs`._ + + +

+ maxTabs1 +

+ +--- + +


+ +# `autoFocus` +Just auto focus to prompt input field after every response arrival or initialization. + +default: `true` + +--- +

+ feedbackOptions +

+


+ +# `userInputLengthWarningThreshold` +The amount of characters in the prompt input necessary for the character limit warning overlay to show up. +> [!NOTE] +> In older versions, the character count used to always show underneath the input, but this was changed in a recent release. + +default: `3500` + +

+ feedbackOptions +

+ +--- + +


+ +# `maxUserInput` +Max number of chars user can insert into the prompt field. But, as might know you can also add code attachments under the prompt field. A treshold of `96` chars will be automatically reduced from the `maxUserInput`. + +**So beware that if you want 4000 chars exact, you need to give 4096 to the config.** + +default: `4096` + +--- + +## `codeInsertToCursorEnabled` and `codeCopyToClipboardEnabled` (default: true) +These two parameters allow you to make copy and insert buttons disabled system wide. If you want to disable it specifically for a message you can do it through `ChatItem` object. Please see [DATAMODEL Documentation](./DATAMODEL.md#codeinserttocursorenabled-and-codecopytoclipboardenabled-default-true). + +

+ codeInsertAndCopy +

+ +--- + +## `codeBlockActions` +With this parameter, you can add global code block actions to the code blocks. But, you can override them through [ChatItem Data Model](./DATAMODEL.md#codeBlockActions). + +### Note +If you want to show that action only for certain coding languages, you can set the array for `acceptedLanguages` parameter. Keep in mind that it will check an exact mathc. If the incoming language is same with one of the acceptedLanguages list, it will show the action. + +#### flash +You can also make the code block actions flash once or foverer when user hovers the the containing card. Until user hovers to the action itself, whenever they hover to the card it will flash the code block action. It you set it to `once` it will only flash once for every hover to the container card, if you set it to `infinite` it will keep flashing forever every 3 seconds until user hovers to the action itself. Whe user hovers to the action, it will not flash again. + +#### By default, we add `copy` and `insert to cursor position` ones: + +```typescript +{ + codeBlockActions: { + ...(codeCopyToClipboardEnabled !== false + ? { + copy: { + id: 'copy', + label: texts.copy, + icon: MynahIcons.COPY + } + } + : {}), + ...(codeInsertToCursorEnabled !== false + ? { + 'insert-to-cursor': { + id: 'insert-to-cursor', + label: texts.insertAtCursorLabel, + icon: MynahIcons.CURSOR_INSERT + } + } + : {}), + } +} +``` + +

+ codeInsertAndCopy +

+ +--- + +


+ +# `showPromptField` +Show or hide the prompt input field completely. You may want to hide the prompt field by setting `showPromptField` to `false` to make the chat work one way only. Just to provide answers or information. + +default: `true` + +_If you set `showPromptField` to `false`_ + +

+ noPrompt +

+ +--- + +## dragOverlayIcon + +**Type:** `MynahIcons | MynahIconsType | CustomIcon` + +**Description:** +Specifies the icon to display in the drag-and-drop overlay for adding files (such as images) to the chat context. This allows consumers to customize the overlay icon. + +**Default:** `MynahIcons.IMAGE` + +

+ noPrompt +

+ +## enableSearchKeyboardShortcut + +**Type:** `boolean` + +When set to `true`, this option enables capturing the search keyboard shortcut. When enabled, pressing Command+F (Mac) or Ctrl+F (Windows/Linux) will trigger the `onSearchShortcut` event instead of the browser's default search behavior. This allows implementing custom search functionality within the chat interface. + +Default: `false` \ No newline at end of file diff --git a/vendor/mynah-ui/docs/DATAMODEL.md b/vendor/mynah-ui/docs/DATAMODEL.md new file mode 100644 index 00000000..9b95f364 --- /dev/null +++ b/vendor/mynah-ui/docs/DATAMODEL.md @@ -0,0 +1,3816 @@ +# MynahUI Data Model (with how the things appear on screen) + +There are a number of models for the various items on the screen for MynahUI. Let's start from the top and go in detail one-by-one. + +## Tab Data Store + +All information you can set related to a tab. + +```typescript +interface MynahUIDataModel { + /** + * Tab title + * */ + tabTitle?: string; + /** + * Tab icon + * */ + tabIcon?: MynahIcons | MynahIconsType | null; + /** + * is tab pinned + * */ + pinned?: boolean; + /** + * Tab title + * */ + tabBackground?: boolean; + /** + * If tab is running an action (loadingChat = true) this markdown will be shown before close in a popup + */ + tabCloseConfirmationMessage?: string | null; + /** + * Keep tab open button text + */ + tabCloseConfirmationKeepButton?: string | null; + /** + * Close tab button text + */ + tabCloseConfirmationCloseButton?: string | null; + /** + * Chat screen loading animation state (mainly use during the stream or getting the initial answer) + */ + loadingChat?: boolean; + /** + * Show chat avatars or not + * */ + showChatAvatars?: boolean; + /** + * Show cancel button while loading the chat + * */ + cancelButtonWhenLoading?: boolean; + /** + * Quick Action commands to show when user hits / to the input initially + */ + quickActionCommands?: QuickActionCommandGroup[]; + /** + * Context commands to show when user hits @ to the input any point + */ + contextCommands?: QuickActionCommandGroup[]; + /** + * Placeholder to be shown on prompt input + */ + promptInputPlaceholder?: string; + /** + * Prompt input text + */ + promptInputText?: string; + /** + * Label to be shown on top of the prompt input + */ + promptInputLabel?: string | null; + /** + * Label to be shown on top of the prompt input + */ + promptInputVisible?: boolean; + /** + * Info block to be shown under prompt input + */ + promptInputInfo?: string; + /** + * A sticky chat item card on top of the prompt input + */ + promptInputStickyCard?: Partial | null; + /** + * Prompt input field disabled state, set to tru to disable it + */ + promptInputDisabledState?: boolean; + /** + * Prompt input progress field + */ + promptInputProgress?: ProgressField | null; + /** + * Prompt input options/form items + */ + promptInputOptions?: FilterOption[] | null; + /** + * Prompt input button items + */ + promptInputButtons?: ChatItemButton[] | null; + /** + * List of chat item objects to be shown on the web suggestions search screen + */ + chatItems?: ChatItem[]; + /** + * Attached code under the prompt input field + */ + selectedCodeSnippet?: string; + /** + * Tab bar buttons next to the tab items + */ + tabBarButtons?: TabBarMainAction[]; + /** + * Tab content compact mode which keeps everything in the middle + */ + compactMode?: boolean; + /** + * Tab content header details, only visibile when not null / undefined + */ + tabHeaderDetails?: TabHeaderDetails | null; + /** + * A lightweight key-value store for essential tab-specific primitive metadata. + * Not intended for storing large amounts of data - use appropriate + * application state management for that purpose. + */ + tabMetadata?: { [key: string]: string | boolean | number }; + /** + * Custom context commands to be inserted into the prompt input. + */ + customContextCommand: [] +} +``` + +You can set tab data with this model for `defaults`, initial `tabs` which can be set through [Constructor Properties](./PROPERTIES.md) or update a tab on runtime by using `mynahUI.updateStore(...)`. + +Let's see which items is what. + +### `tabTitle` (default: `"AWS Q"`) +Basically it is the tab title. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + tabTitle: 'Chat' +}) +``` + +### `tabIcon` (default: undefined) +Basically it is an icon you can give to the tab. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + tabTitle: '', + tabIcon: MynahIcons.MENU, + pinned: true +}) +``` + +### + +

+ pinnedTab +

+ + +### `pinned` (default: `false`) +You can pin the tabs to the beginning. But when you pin a tab, end user cannot close them anymore. It will disable the middle mouse click to close a tab and remove the close button too. The tab will be basically pinned. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + tabTitle: '', + tabIcon: MynahIcons.MENU, + pinned: true +}) +``` + +### + +

+ pinnedTab +

+ +### `tabBackground` (default: `false`) +Shows or hides the gradient background on the tab. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + tabBackground: true +}) +``` + + +### + +

+ tabBackground +

+ +--- + +### `tabCloseConfirmationMessage`, `tabCloseConfirmationKeepButton` and `tabCloseConfirmationCloseButton` + +Custom texts for each tab for the message and the buttons of the popup to confirm the tab close. Check **[Config/TEXTS](./CONFIG.md#texts)** for defaults. + +

+ onTabRemove +

+ +--- + +### `loadingChat` (default: `false`) +Basically it is the tab title. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + loadingChat: true +}) +``` +When you set `loadingChat` to true, if there is a streaming card it will start to animate the spinner in two different way. If the card body is empty it will show the `spinnerText` from the texts inside the config right next to a spinning circle. If the card has a body (after it is updated for example) it will show a sliding colored bottom border animation. + +In addition to the spinner, if `onStopChatResponse` is attached globally through MynahUI main class constructor properties _(see [Constructor properties](./PROPERTIES.md) for details)_ and `cancelButtonWhenLoading` is not set to false specifically for that tab it will show the stop generating button too. + +

+ mainTitle +

+

+ mainTitle +

+ +--- + + +### `cancelButtonWhenLoading` (default: `true`) +If `onStopChatResponse` is attached globally through `MynahUI` main class constructor properties _(see [Constructor properties](./PROPERTIES.md) for details)_ it will show a stop generating button to let the user cancel the ongoing action. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + loadingChat: true, + cancelButtonWhenLoading: true +}) +``` + +

+ mainTitle +

+ +--- + + +### `quickActionCommands` (default: `[]`) +Quick action commands are the predefined commands which user can pick between. When users hit `/` from their keyboard as the initial char in the input, if there is an available list it will show up as a overlay menu. + +If you want a command immediately run after the selection and trigger `onChatPrompt` event (attached to the `MynahUI` main instance through the [Constructor properties](./PROPERTIES.md)) leave the `placeholder` attribute undefined. MynahUI will decide that it doesn't allow additional prompt text for that command and immediately run the trigger. _(See command-2 in the example)_ + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + quickActionCommands: [ + { + icon: MynahIcons.CODE, + groupName: 'Command Group 1', + commands: [ + { + command: '/command-1', + placeholder: 'Command which accepts a prompt after the command selection', + description: 'Command 1 description', + }, + { + command: '/command-2', + description: 'Command 2 description', + }, + ], + }, + { + groupName: 'Command Group 2', + commands: [ + { + command: '/command-3', + placeholder: 'Command which accepts a prompt after the command selection', + description: 'Command 3 description', + }, + ], + }, + { + // Command Group without title + commands: [ + { + command: '/command-4', + placeholder: 'Command which accepts a prompt after the command selection', + description: 'Command 4 description', + }, + ], + }, + ] +}) +``` + +

+ quickActionCommands +

+ +To handle the incoming command (if there is) check it with the prompt object in the `onChatPrompt` event. + +```typescript +const mynahUI = new MynahUI({ + ... + onChatPrompt: (prompt)=>{ + if(prompt.command !== undefined){ + switch (prompt.command) { + case '/command-1': + console.log(`Command 1 selected with prompt: ${prompt.prompt}`); + break; + case '/command-2': + console.log('Command 2 selected'); + break; + default: + ... + break; + } + } + } +}); +``` + +--- + +### `contextCommands` (default: `[]`) +Context commands are the predefined context items which user can pick between but unlike quick action commands, they can be picked several times at any point in the prompt text. When users hit `@` from their keyboard in the input, if there is an available list of context items provided through store it will show up as an overlay menu. + +#### Disabled Commands +Commands can be disabled by setting the `disabled` property to `true`. When a command is disabled, you can optionally provide `disabledText` to display custom text instead of the usual arrow icon for items with children. This is useful for showing status information like "pending". + + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { +contextCommands: [ + { + commands: [ + { + command: 'workspace', + icon: MynahIcons.ASTERISK, + placeholder: 'Yes, you selected workspace :P', + description: 'Reference all code in workspace.', + disabled: true, + disabledText: 'pending' + }, + { + command: 'folder', + icon: MynahIcons.FOLDER, + children: [ + { + groupName: 'Folders', + commands: [ + { + command: 'src', + icon: MynahIcons.FOLDER, + children: [ + { + groupName: 'src/', + commands: [ + { + command: 'index.ts', + icon: MynahIcons.FILE, + } + ] + } + ] + }, + { + command: 'main', + description: './src/', + icon: MynahIcons.FOLDER, + }, + { + command: 'src', + description: './example/', + icon: MynahIcons.FOLDER, + } + ] + } + ], + placeholder: 'Mention a specific folder', + description: 'All files within a specific folder' + }, + { + command: 'file', + icon: MynahIcons.FILE, + children: [ + { + groupName: 'Files', + commands: [ + { + command: 'monarch.ts', + description: './src/', + icon: MynahIcons.FILE, + }, + { + command: '_dark.scss', + description: './src/styles/', + icon: MynahIcons.FILE, + } + ] + } + ], + placeholder: 'Mention a specific file', + description: 'Reference a specific file' + }, + { + command: 'symbols', + icon: MynahIcons.CODE_BLOCK, + children: [ + { + groupName: 'Symbols', + commands: [ + { + command: 'DomBuilder', + icon: MynahIcons.CODE_BLOCK, + description: 'The DomGeneration function in dom.ts file' + } + ] + } + ], + placeholder: 'Select a symbol', + description: 'After that mention a specific file/folder, or leave blank for full project' + }, + { + command: 'prompts', + icon: MynahIcons.CHAT, + description: 'Saved prompts, to reuse them in your current prompt', + children: [ + { + groupName: 'Prompts', + actions: [ + { + id: 'add-new-prompt', + icon: 'plus', + text: 'Add', + description: 'Add new prompt' + } + ], + commands: [ + { + command: 'python_expert', + icon: MynahIcons.CHAT, + description: 'Expert on python stuff' + }, + { + command: 'javascript_expert', + icon: MynahIcons.CHAT, + description: 'Expert on Javascript and typescript' + }, + { + command: 'Add Prompt', + icon: MynahIcons.PLUS, + } + ] + } + ] + } + ] + } + ] +}) +``` + +

+ contextCommands +

+ +When hovered, context items will display a tooltip with the same information provided in the context menu list: + +

+ contextItem +

+ +Groups can have as many children as you'd like, which allows for a tree-like structure. Items with children will display a right-arrow icon when hovered / focused: + +

+ hoveredContextItem +

+ +Groups can have actions (see `add-new-prompt` action in the example code block above), which adds an action button on the top right: + +

+ groupAction +

+ +To see which context is used, check the incoming string array in the prompt object comes with the `onChatPrompt` event. + +```typescript +const mynahUI = new MynahUI({ + ... + onChatPrompt: (prompt)=>{ + if(prompt.context != null && prompt.context.indexOf('@ws') { + // Use whole workspace! + } + } +}); +``` + +--- + +### `promptInputPlaceholder` (default: `''`) + +This is the placeholder text for the prompt input + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptInputPlaceholder: 'Ask a question or “/” for capabilities' +}) +``` + +

+ mainTitle +

+ +--- +### `promptTopBarTitle` (default: `''`) + +This is the title displayed in the prompt top bar. When set, it enables a top bar that can be used for pinned context items. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptTopBarTitle: '@Pin Context' +}) +``` + +

+ prompt top bar title +

+ +--- + +### `promptTopBarContextItems` (default: `[]`) + +These are the context items pinned to the prompt top bar. They appear as pills that can be removed by the user. Top bar only appears when `promptTopBarTitle` is not empty. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptTopBarContextItems: [ + { + command: 'ex-dom.ts', + icon: MynahIcons.FILE, + description: '.src/helper' + }, + { + command: 'main', + icon: MynahIcons.FOLDER, + description: '.src/' + } + ] +}) +``` + +

+ prompt top bar context items +

+ +--- + +### `promptTopBarButton` (default: `null`) + +This is a button displayed at the end of the prompt top bar. Clicking on the button will call onPromptTopBarButtonClick(). Button only appears when `promptTopBarTitle` is not empty. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptTopBarButton: { + id: 'project-rules', + icon: MynahIcons.CHECK_LIST, + text: 'Rules' + } +}) +``` + +

+ prompt top bar button +

+ +--- + +### `promptInputText` (default: `''`) + +This is the text inside the prompt input. You can set it anytime, but be careful, it will override what is already written in the text input. +A nice trick to use it is to open the quick actions command picker too. If you send `"/"` or `"/some-matching-text"` it will open the quick actions command selector automatically and also filter the list with the following text if given. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptInputText: '/dev' +}) +``` + +

+ Prompt input text +

+ +--- + +### `promptInputLabel` (default: `''`) + +This is label for the prompt input text. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptInputLabel: 'Prompt input text label' +}) +``` + +

+ prompt input label +

+ +--- + +### `promptInputVisible` (default: `true`) + +This is a control point for the visibility of the prompt input field. Unlike the `showPromptField` in [global CONFIG](./CONFIG.md#showpromptfield) it allows you to change the visibility of the prompt input field for each individual tab on runtime. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptInputVisible: false, +}) +``` + +

+ mainTitle +

+ +--- + +### `promptInputInfo` (default: `''`) + +This is a info field under the bottom of the prompt input field, like a footer text + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptInputInfo: 'Use of Amazon Q is subject to the [AWS Responsible AI Policy](https://aws.com).', +}) +``` + +

+ mainTitle +

+ +--- + +### `promptInputStickyCard` (default: `null`) + +This is a chat item card which will be shown on top of the prompt input field. Main usage scneario for this is to inform the user with a card view, which means that it can also have some actions etc. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + }, + ... + onInBodyButtonClicked: (tabId: string, messageId: string, action) => { + if(messageId === 'sticky-card'){ + // clear the card + mynahUI.updateStore(tabId, {promptInputStickyCard: null}); + } + ... + }, + ... +}); + +mynahUI.updateStore(tabId, { + promptInputStickyCard: { + messageId: 'sticky-card', + body: `Please read the [terms and conditions change](#) and after that click the **Acknowledge** button below!`, + status: 'info', + icon: MynahIcons.INFO, + buttons: [ + { + // you can also simply set this to false to remove the card automatically + keepCardAfterClick: true, + text: 'Acknowledge', + id: 'acknowledge', + status: 'info', + icon: MynahIcons.OK + }, + ], + } +}); + +``` + +

+ mainTitle +

+ +--- + +### `promptInputDisabledState` (default: `false`) + +This is the disabled state if the prompt input field. When set to true, user cannot focus to the input and cannot click to the send button. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + promptInputDisabledState: true, +}) +``` + +

+ mainTitle +

+ +--- + +### `promptInputProgress` + +This determines whether the progress bar shows up, and what its styling and progress value is. The `value` should be a number representing the progress, and the `valueText` is the text that shows right next to the regular `text` to indicate the progress in the bar. A number of `actions` can be added to dispatch events. Different statuses are available, namely: `default` | `info` | `success` | `warning` | `error`. + +**In progress:** +```typescript +mynahUI.updateStore('tab-1', { + promptInputProgress: { + status: 'default', + text: 'Work in progress...', + value: -1, + actions: [{ + id: 'cancel-running-task', + text: 'Cancel', + icon: MynahIcons.CANCEL, + disabled: false, + }] + } +}); +``` + +**Completed:** +```typescript +mynahUI.updateStore('tab-1', { + promptInputProgress: { + status: 'success', + text: 'Completed...', + valueText: '', + value: 100, + actions: [] + } +}); +``` + +

+ mainTitle +

+ +--- + +### `promptInputOptions` + +Under the prompt input field, it is possible to add form items too for several options. For example a toggle can be placed to let user pick the type of the prompt. To listen the value changes on these options please check [onPromptInputOptionChange in Constructor properties](./PROPERTIES.md#onPromptInputOptionChange) and the see how they are being passed to prompt please check [onChatPrompt in Constructor properties](./PROPERTIES.md#onChatPrompt). + +To cleanup, simply set to `null` or an empty array. + +```typescript +mynahUI.updateStore('tab-1', { + promptInputOptions: [ + { + type: 'toggle', + id: 'prompt-type', + value: 'ask', + options: [{ + value: 'ask', + icon: MynahIcons.CHAT + },{ + value: 'do', + icon: MynahIcons.FLASH + }] + } + ] +}); +``` + +

+ promptOptions +

+ +------ + +### `promptInputButtons` + +Under the prompt input field, it is possible to add buttons too. To listen the click events on these options please check [onPromptInputButtonClick in Constructor properties](./PROPERTIES.md#onPromptInputButtonClick). + +To cleanup, simply set to `null` or an empty array. + +```typescript +mynahUI.updateStore('tab-1', { + promptInputButtons: [ + { + id: 'upgrade-q', + icon: 'bug', + } + ] +}); +``` + +

+ promptButtons +

+ +--- + +### `selectedCodeSnippet` + +This is the attached code block text right under the prompt input field.. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + selectedCodeSnippet: `const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + isSelected: true, + .....`, +}); +``` + +

+ mainTitle +

+ +--- + +### `tabBarButtons` + +You can put buttons on the right of the tab bar also with some inner buttons inside a menu. You can do it in two different ways. If you want the buttons belong to specific tab, you can use the `tabBarButtons` for tab store. If you want them globally available for every tab, check the **[Config Documentation](./CONFIG.md#tabbarbuttons)**. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + tabBarButtons: [ + { + id: 'clear', + description: 'Clear messages in this tab', + icon: MynahIcons.REFRESH, + }, + { + id: 'multi', + icon: MynahIcons.ELLIPSIS, + items: [ + { + id: 'menu-action-1', + text: 'Menu action 1!', + icon: MynahIcons.CHAT, + }, + { + id: 'menu-action-2', + text: 'Menu action 2!', + icon: MynahIcons.CODE_BLOCK, + }, + { + id: 'menu-action-3', + text: 'Menu action 3!' + } + ] + } + ], +}) +``` + +

+ mainTitle +
+ mainTitle +

+ +--- + +### `compactMode` + +You can enable/disable compact mode. In compact mode, there will be more paddings from every edge. In addition to the paddings, the chat content will be middle placed (15% more pushed from the bottom) instead of being stretched to the available height. However, it will not exceed the available height for its own space. +While doing the transition for the compact mode switch, there is also a nice and smooth animation. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + compactMode: true, +}) +``` + +

+ compactMode +

+ +--- + +### `tabHeaderDetails` (default: `null`) + +There is a chance to add a detailed header on top of the tab content. Which can have an icon, title and the description. +**NOTE:** When you give `tabHeaderDetails` it will also adjust the alignment of the chat items to top. So until the content section reaches the max height available, they'll start to be ordered from top to bottom. Which means that it will also take space as their available content height. This will make the prompt field also moves up under the content. If the content height is more than available space, prompt input will still fit under the bottom of the screen. + +**NOTE:** When you provide `tabHeaderDetails` it will also make the chat cards width stretch to full available width of the screen. So they'll not get their width depending on their content and up to 90%. Instead, it will always be 100%. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + tabHeaderDetails: { + icon: MynahIcons.Q, + title: "Welcome to Q Developer", + description: "What kind of questions you have?" + }, +}) +``` + +

+ tabHeaderDetails +
+ tabHeaderDetails 2 +

+ +--- + +### `tabMetaData` (default: `{}`) + +A lightweight key-value store for essential tab-specific metadata. Not intended for storing large amounts of data - use appropriate application state management for that purpose. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + tabMetaData: { + 'test': 'hi' + } +}) +``` + +--- + +### `chatItems` (default: `[]`) + +This is holding the chat items. If you provide it through the `defaults` or inside a tab item in the initial `tabs` property in the [Constructor properties](./PROPERTIES.md) you can give the whole set. + +**BUT** if you will set it through `updateStore` it will append the items in the list to the current chatItems list. In case if you need to update the list with a new one manually on runtime, you need to send an empty list first and than send the desired new list. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.updateStore('tab-1', { + chatItems: [], +}) +``` + +


+ +--- + +


+ +# `ChatItem` (and how they appear on screen) + +There are basically 2 main types of chat items. One is to show a file list and the other one is for all other items with a body of markdown string content. + +Let's start with the model definition: + +```typescript +// There can be more types on the ChatItemType enum list +// However those are the only ones getting used by MynahUI +enum ChatItemType { + CODE_RESULT = 'code-result', + ANSWER_STREAM = 'answer-stream', + DIRECTIVE = 'directive', + ANSWER = 'answer', + PROMPT = 'prompt', + SYSTEM_PROMPT = 'system-prompt' +} + +interface ChatItemAction extends ChatPrompt { + type?: string; + pillText: string; + disabled?: boolean; + description?: string; + status?: 'info' | 'success' | 'warning' | 'error'; + icon?: MynahIcons; +} + +interface ChatItemButton { + keepCardAfterClick?: boolean; + waitMandatoryFormItems?: boolean; + text: string; + id: string; + disabled?: boolean; + description?: string; + status?: 'info' | 'success' | 'warning' | 'error'; + icon?: MynahIcons; +} + +type ChatItemFormItem = TextBasedFormItem | DropdownFormItem | RadioGroupFormItem | CheckboxFormItem | ListFormItem | Stars | PillboxFormItem; + +export interface ValidationPattern { + pattern: string | RegExp; + errorMessage?: string; +} + +interface BaseFormItem { + id: string; + mandatory?: boolean; + hideMandatoryIcon?: boolean; + title?: string; + placeholder?: string; + value?: string; + description?: string; + tooltip?: string; + icon?: MynahIcons | MynahIconsType; + boldTitle?: boolean; +} + +export type TextBasedFormItem = BaseFormItem & { + type: 'textarea' | 'textinput' | 'numericinput' | 'email'; + autoFocus?: boolean; + checkModifierEnterKeyPress?: boolean; + validateOnChange?: boolean; + validationPatterns?: { + operator?: 'and' | 'or'; + genericValidationErrorMessage?: string; + patterns: ValidationPattern[]; + }; + disabled?: boolean; +}; + +type DropdownFormItem = BaseFormItem & { + type: 'select'; + border?: boolean; + autoWidth?: boolean; + options?: Array<{ + value: string; + label: string; + description?: string; + }>; + disabled?: boolean; + selectTooltip?: string; +}; + +type Stars = BaseFormItem & { + type: 'stars'; + options?: Array<{ + value: string; + label: string; + }>; +}; + +type RadioGroupFormItem = BaseFormItem & { + type: 'radiogroup' | 'toggle'; + options?: Array<{ + value: string; + label?: string; + icon?: MynahIcons | MynahIconsType; + }>; + disabled?: boolean; +}; + +type CheckboxFormItem = BaseFormItem & { + type: 'switch' | 'checkbox'; + value?: 'true' | 'false'; + label?: string; + alternateTooltip?: string; +}; + +export interface ListFormItem { + type: 'list'; + id: string; + mandatory?: boolean; + hideMandatoryIcon?: boolean; + title?: string; + description?: string; + tooltip?: string; + icon?: MynahIcons | MynahIconsType; + items: SingularFormItem[]; + value: ListItemEntry[]; + disabled?: boolean; +}; + +export interface ListItemEntry { + persistent?: boolean; + value: Record; +} + +type PillboxFormItem = BaseFormItem & { + type: 'pillbox'; + value?: string; +}; + +interface FileNodeAction { + name: string; + label?: string; + disabled?: boolean; + description?: string; + status?: Status; + icon: MynahIcons | MynahIconsType; +} + +interface TreeNodeDetails { + status?: Status; + icon?: MynahIcons | MynahIconsType | null; + iconForegroundStatus?: Status; + label?: string; + changes?: { + added?: number; + deleted?: number; + total?: number; + }; + description?: string; + clickable?: boolean; + data?: Record; +} + +interface SourceLink { + title: string; + id?: string; + url: string; + body?: string; + type?: string; + metadata?: Record; +} + +interface ReferenceTrackerInformation { + licenseName?: string; + repository?: string; + url?: string; + recommendationContentSpan?: { + start: number; + end: number; + }; + information: string; +} + +interface ChatItemBodyRenderer extends GenericDomBuilderAttributes { + type: AllowedTagsInCustomRenderer; + children?: Array | undefined; + attributes?: Partial> | undefined; +} + +interface CodeBlockAction { + id: 'copy' | 'insert-to-cursor' | string; + label: string; + description?: string; + icon?: MynahIcons; + data?: any; + flash?: 'infinite' | 'once'; + acceptedLanguages?: string[]; +} +type CodeBlockActions = Record<'copy' | 'insert-to-cursor' | string, CodeBlockAction | undefined | null>; + +// ################################# +interface ChatItemContent { + header?: (ChatItemContent & { + icon?: MynahIcons | MynahIconsType | CustomIcon; + iconStatus?: 'main' | 'primary' | 'clear' | Status; + iconForegroundStatus?: Status; + status?: { + status?: Status; + position?: 'left' | 'right'; + description?: string; + icon?: MynahIcons | MynahIconsType; + text?: string; + }; + }) | null; + body?: string | null; + customRenderer?: string | ChatItemBodyRenderer | ChatItemBodyRenderer[] | null; + followUp?: { + text?: string; + options?: ChatItemAction[]; + } | null; + relatedContent?: { + title?: string; + content: SourceLink[]; + } | null; + codeReference?: ReferenceTrackerInformation[] | null; + fileList?: { + fileTreeTitle?: string; + rootFolderTitle?: string; + rootFolderStatusIcon?: MynahIcons | MynahIconsType; + rootFolderStatusIconForegroundStatus?: Status; + rootFolderLabel?: string; + filePaths?: string[]; + deletedFiles?: string[]; + flatList?: boolean; + folderIcon?: MynahIcons | MynahIconsType | null; + collapsed?: boolean; + hideFileCount?: boolean; + renderAsPills?: boolean; // When true (header only), renders files as inline pills instead of tree + actions?: Record; + details?: Record; + } | null; + buttons?: ChatItemButton[] | null; + formItems?: ChatItemFormItem[] | null; + footer?: ChatItemContent | null; + informationCard?: { + title?: string; + status?: { + status?: Status; + icon?: MynahIcons | MynahIconsType; + body?: string; + }; + description?: string; + icon?: MynahIcons | MynahIconsType; + content: ChatItemContent; + } | null; + summary?: { + isCollapsed?: boolean; + content?: ChatItemContent; + collapsedContent?: ChatItemContent[]; + } | null; + tabbedContent?: Array | null; + codeBlockActions?: CodeBlockActions | null; + quickSettings?: DropdownFactoryProps | null; + fullWidth?: boolean; + padding?: boolean; + wrapCodes?: boolean; + muted?: boolean; +} + +interface ChatItem extends ChatItemContent { + type: ChatItemType; + messageId?: string; + snapToTop?: boolean; + autoCollapse?: boolean; + contentHorizontalAlignment?: 'default' | 'center'; + canBeVoted?: boolean; + canBeDismissed?: boolean; + title?: string; + icon?: MynahIcons | MynahIconsType | CustomIcon; + iconForegroundStatus?: Status; + iconStatus?: 'main' | 'primary' | 'clear' | Status; + hoverEffect?: boolean; + status?: Status; + shimmer?: boolean; +} +// ################################# +``` + +Let's see all kind of examples and what parameter reflects to what. + +## `type` + +### ChatItemType.`ANSWER_STREAM` _(position: left)_ +Use for streaming cards. It is better to start with an empty string to let the initial spinner rotate. As far as the `loadingState` is true for the tab which holds this chat item, it will show the spinner (rotating circle for empty state and bottom border for with a body). + +When you add a new chat item with type `ANSWER_STREAM` MynahUI will set it as the streaming card and when you call `updateLastChatAnswer` it will update this. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.ANSWER_STREAM, + body: '' +}); + +// After a moment +mynahUI.updateLastChatAnswer('tab-1', { + body: `### How to create a React stateless function component + +*React .14* introduced a simpler way to define components called stateless functional components. + ` +}); +``` + +

+ mainTitle +

+ +--- + + + +### ChatItemType.`DIRECTIVE` _(position: left)_ +Use for directions. Those chat item cards will not have a background, will not have a padding and border at all. But they'll support all chatitem functionalities as is. + + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.DIRECTIVE, + body: '_Starting with a directive_' +}); +``` + +

+ directive +

+ +--- + +### ChatItemType.`ANSWER` or ChatItemType.`CODE_RESULT` _(position: left)_ +Use for all kind of answers. Including the followups etc. + +And yes, except the `fileList` you can combine followups and markdown string content chat items at once. Which means that a single chat item can also contain the `followUp` at the same time with `body`. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.ANSWER, + body: 'Hi, I\'m Amazon Q. I can answer your software development questions. Ask me to explain, debug, or optimize your code. You can enter `/` to see a list of quick actions.' + followUp:{ + text: 'Or you can select one of these', + options: [ + { + pillText: 'Explain selected code', + }, + { + pillText: 'How can Amazon Q help me?', + prompt: 'How can Amazon Q help me?', + } + ], + } +}); +``` + +

+ mainTitle +

+ +--- + +### ChatItemType.`PROMPT` _(position: right)_ +Use for user prompts. You can also send followups to let them appear on right. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.PROMPT, + body: 'Who are you?' +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.PROMPT, + followUp:{ + text: 'Or you can select one of these', + options: [ + { + pillText: 'Explain selected code', + }, + { + pillText: 'How can Amazon Q help me?', + prompt: 'How can Amazon Q help me?', + } + ], + } +}); +``` + +

+ mainTitle +

+ +--- + +### ChatItemType.`SYSTEM_PROMPT` _(position: right)_ +Use for sysyem prompts. Only difference with `PROMPT` is the color of the chat card. (Depends on your **[Styling Configuration](STYLING.md)**) You can also send followups to let them appear on right. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.SYSTEM_PROMPT, + body: 'This is a system prompt' +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.SYSTEM_PROMPT, + followUp: { + text: 'Or you can select one of these', + options: [ + { + pillText: 'Explain selected code', + }, + { + pillText: 'How can Amazon Q help me?', + prompt: 'How can Amazon Q help me?', + } + ], + } +}); +``` + +

+ mainTitle +

+ +--- + +## `header` +With this parameter, you can add a `ChatItem` at the top of a ChatItem, before the body, but still within the card itself. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER, + body: `SOME CONTENT`, + header: { + // icon: MynahIcons.CODE_BLOCK; + // status: { + // position: 'right', + // status: 'success', + // icon: MynahIcons.OK, + // text: 'Accepted', + // }, + fileList: { // For example, want to show which file is used to generate that answer + rootFolderTitle: undefined, + fileTreeTitle: '', + filePaths: ['./src/index.ts'], + details: { + './src/index.ts': { + icon: MynahIcons.FILE, + description: `SOME DESCRIPTION.` + } + } + } + } +}); +``` + +

+ header +

+ +You can also provide an icon specifically for the header, as well as a separate status section on right or left of the whole header defined by its `position` value with a tooltip too. + +Here's another example for that: + +```typescript +mynahUI.addChatItem(tabId, { + messageId: 'MY_UNIQUE_ID', + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: MynahIcons.CODE_BLOCK, + status: { + position: 'right', + icon: MynahIcons.PROGRESS, + description: 'Hello!', + text: 'Working', + status: 'warning' + }, + buttons: [{ + id: 'stop', + icon: MynahIcons.CANCEL, + }], + fileList: { + fileTreeTitle: '', + filePaths: ['package.json'], + details: { + 'package.json': { + icon: null, + label: 'Creating', + changes: { + added: 36, + deleted: 0, + total: 36 + } + } + } + } + } +}); +``` + + +

+ headerMore +

+--- + +## `body` +Basically the body of the card. Which you can send a full markdown string. Allows code blocks, links etc. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem('tab-1', { + type: ChatItemType.ANSWER, + body: "## Here'a heading 2\nAnd also here are some code blocks which supports most common languages.\n```typescript\nconst a = 5;\n```\n You can also use some `inline code` items too.\n And also for example [a link](https://aws.com)" +}); +``` + +

+ mainTitle +

+ +--- + +## `customRenderer` +Custom renderers can be provided in 3 different types *(string, ChatItemBodyRenderer object or ChatItemBodyRenderer object array)* and they are here to help you in case you need to create some static content on the client side rather than a data arrived from the backend. Or, maybe it is not possible or so hard to do it just with markdown. + +##### Note: It can be combined with `body`, so you don't need to choose one of them. + + +### Using with `string` type +If you give a string to the `customRenderer` mynah-ui will consider that it as an html markup string and will render it that way. + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +mynahUI.addChatItem('tab-1', { + messageId: (new Date().getTime()).toString(), + type: ChatItemType.ANSWER, + canBeVoted: true, + customRenderer: ` +

Custom renderer's with HTML markup string

+

+ Here you will find some custom html rendering examples which may not be available with markdown or pretty hard to generate. +

+
+ +

Table (inside a blockqote)

+
+ Most popular JS frameworks + + +
+ + + + + + + + + + + + + + + + + + + + + + +
Name + Weekly Downloads +
Vanillainf.
React24 million
JQuery10.6 million
VUE4.75 million
+
+
+ + +
+
+ +

Code block and Links

+ +
+            import { MynahUI } from '@aws/mynah-ui';
+
+            const mynahUI = new MynahUI({});
+        
+

+ You can find more information and references + + HERE! + . +

+ +
+ + +
+
+ +

Embeds and Media elements

+ +

Iframe embed (Youtube example)

+ +
+ +

Video element

+ +
+ +

Audio element

+ +
+ +

Image

+ Powered by AWS +
+ + +
+
+ +

+ There might be infinite number of possible examples with all supported tags and their attributes. + It doesn't make so much sense to demonstrate all of them here. + You should go take a look to the + + documentation + + for details and limitations. +

` +}); +``` + +

+ customRendererHTML +

+ +### Using with `ChatItemBodyRenderer` or `ChatItemBodyRenderer[]` type +Even though you can build exactly the same html elements and node tree with the `string` type, this option will give you more flexibility especially on repeating items. We all know that it is not easy to read code which loops inside a string. **But more importantly, you can also bind events with this option**. + +Another `+1` for this option is related with its interface declaration. With an object structure which is properly typed, your IDE should give you the available values list during the code completion. Which means that you don't need to guess or go back and forth between the documentation and your project to see which tags you can use in the `type` attribute (html tag), which attributes are supported for the `attributes` or which events are available for the `events`. + +Let's take a look how we write with `ChatItemBodyRenderer[]` interface: + +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + ... + } + } +}); + +// Lets' use a super dumb array instead of copy pasting the items inside the customRenderer. +const topFrameworks: Record = {'Vanilla': 'inf.', 'React': '24', 'JQuery': '10.6', 'VUE': '4.75'}; + +mynahUI.addChatItem('tab-1', { + messageId: (new Date().getTime()).toString(), + type: ChatItemType.ANSWER, + canBeVoted: true, + customRenderer: [ + { + type: 'h3', + children: ['Custom renderer\'s with JSON dom builder objects'] + }, + { + type: 'p', + children: ['Here you will find some custom html rendering examples which may not be available with markdown or pretty hard to generate. But in this examples they are rendered from JSON definitions.'] + }, + { + type: 'p', + children: ['There is no difference between using a markup string or a JSON dom. You can create same accepted tags with same accepted attributes.'] + }, + { + type: 'p', + children: [ + 'Except 1 thing: ', + {type: 'strong', children: ['attaching events! Like click or mousemove etc.']} + ] + }, + { type: 'br' }, + { + type: 'h3', + events: { + click: (event) => { alert('Why you click to title?'); } + }, + children: ['Table (inside a blockqote)'] + }, + { + type: 'p', + children: ['This is basically the same table one card above with markup strings, but in this one ', {type: 'b', children: ['you can click to the table titles!']}] + }, + { type: 'br' }, + { + type: 'blockquote', + children: [ + 'Most popular JS frameworks', + { type: 'hr' }, // Divider + { + type: 'table', + children: [ + { + type: 'tr', + children: [ + { + type: 'th', + events: { + click: () => { alert('Why you click this title?'); } + }, + attributes: { align: 'left' }, + children: ['Name'] + }, + { + type: 'th', + events: { + click: () => { alert('Why you click to this title?'); } + }, + attributes: { align: 'right' }, + children: ['Weekly Downloads'] + } + ] + }, + // Mapping our dumb array to create the rows + ...Object.keys(topFrameworks).map(fw => ({ + type: 'tr', + children: [ + { type: 'td', children: [fw]}, + { type: 'td', + attributes: { align: 'right' }, + children: [ + topFrameworks[fw], + ...(!isNaN(parseFloat(topFrameworks[fw])) ? [{type: 'small', children: [' million']}] : []) + ] + } + ] + } as ChatItemBodyRenderer + )), + ] + } + ] + }, + { type: 'br' }, // Add more space + { + type: 'p', + children: ['Or you can click below image to remove it!'] + }, + { type: 'br' }, + { + type: 'img', + events: { + click: (event: MouseEvent)=>{ + (event.target as HTMLElement).remove(); + } + }, + attributes: { + src: 'https://d1.awsstatic.com/logos/aws-logo-lockups/poweredbyaws/PB_AWS_logo_RGB_stacked_REV_SQ.91cd4af40773cbfbd15577a3c2b8a346fe3e8fa2.png', + alt: 'Powered by AWS!' + } + } + ] + }); +``` + +

+ customRendererJson +

+ +## BUT: There are some `LIMITATIONS!` + +### We know that you're extremely careful while building custom html blocks and you're an expert on CSS. However, we still need to assure that the look & feel of the UI is not broken and it works as expected with all the functionalities. Because of these reasons with the addition of the safety concerns we have to **`sanitize`** the HTML contents you provide. + +**And,** the sanitization requirement it is not just limited with the above. We're also automatically applying the functionalities we have on the original chat item body like *highlighting the code syntaxes, adding copy to clipboard and insert at cursor position buttons or adding the event controls for links etc.*. +For example, you can check how the code blocks provided inside `customRenderer` look like (and do they have the copy buttons?) in the above examples. + +**NOTE:** Below limitations are applicable for all of the `string`, `ChatItemBodyRenderer` and `ChatItemBodyRenderer[]` type usages. + +### List of available tags: + +``` +[ + 'a', 'audio', 'b', 'blockquote', + 'br', 'hr', 'canvas', + 'code', 'col', 'colgroup', + 'data', 'div', 'em', + 'embed', 'figcaption', 'figure', + 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', + 'i', 'iframe', + 'img', 'li', 'map', + 'mark', 'object', 'ol', + 'p', 'pre', 'q', + 's', 'small', 'source', + 'span', 'strong', 'sub', + 'sup', 'table', 'tbody', + 'td', 'tfoot', 'th', + 'thead', 'tr', 'track', + 'u', 'ul', 'video', +] +``` + +**NOTE:** As you can see in the above list, **form items are also not available**. Since this is a chat interface we should keep it as conversational as possible instead of using select/input/click structures to interact if they are not helping the end user. But in case you need some small forms and inputs from the user other than the prompts, you can use the **[`formIems`](#formitems)**. + +### List of available attributes: + +``` +[ + 'accept','accept-charset','accesskey', + 'align','allow','allowfullscreen', + 'alt', 'as','async','autocapitalize', + 'autoplay','charset','class', + 'cols','colspan','controls', + 'crossorigin','data','data-*', + 'datetime','decoding','default', + 'dir','download','headers', + 'hidden','high','href', + 'hreflang','id','ismap',' + itemprop','kind','lang', + 'language','loop','low', + 'media','muted','optimum', + 'ping','playsinline','poster', + 'preload','referrerpolicy', + 'rel','reversed','role', + 'rowspan','sandbox','scope', + 'shape','size','sizes','slot', + 'span','spellcheck','src', + 'srcdoc','srclang','srcset', + 'start', 'style', 'target','title', + 'translate','usemap', + 'wrap','aspect-ratio' +] +``` + +## Important Tips for `customRenderer` + +### Tip 1 +As you might see there is also no `width` and `height` attributes are available. +As we've told you above, we know you're so good at styling components but our concern is the HTML itself. Since `mynah-ui` has a responsive design nature, we cannot let you write a static width or height to an `img` for example. + +### But you're free to write custom styles for each tag you can create. But don't forget that you're getting the responsibility of a broken UI. So be careful with the styles and try not to be so extreme on that. + +It applies to `iframe`s, `video`s and other similar media elements too. +So, **avoid writing static sizes** and learn **what is the aspect ratio of your media content**. + +### Tip 2 +In general, those items *(except `img`)* will automatically stretched to 100% width and will stay that way as the max width is 100%. Yes, you cannot use static width and heights, **but** you can define their aspect ratios. Here's an example: + +``` + +``` +When you provide a value to the `aspect-ratio` attribyte, it will automatically set the `width` of the element to `100%` and apply the aspect ratio for the height. + +### Tip 3 +So, are you looking for the available `aspect-ratio` values? +Here they are: `16:9`, `9:16`, `21:9`, `9:21`, `4:3`, `3:4`, `3:2`, `2:3`, `1:1` + +If you need more aspect-ratios, please raise a feature request. + +### Tip 4 +**But,** of course we cannot control your imagination and lower down your expertise on html element structures. + +For example; you can say that oldies are goldies and still have some emotional connection to the `table`s. How we can understand that you used a `table` and used some `colspan`s for the `td`s to adjust the width as the half of the wrapper card for the element you put inside which will not break the responsive structure... + +``` + + +
+ +
+

Video element

+ +
+

Audio element

+ +
+

Image

+Powered by AWS + +
+
+
+ +

There might be infinite number of possible examples with all supported tags and their attributes. It doesn't make so much sense to demonstrate all of them here. +You should go take a look to the documentation for details and limitations.

+`, + }; +}; + +const attachmentIcon = ` + + +`; +export const exampleCustomRendererWithDomBuilderJson: ChatItem = { + messageId: new Date().getTime().toString(), + type: ChatItemType.ANSWER, + canBeVoted: true, + body: `Your Refactor analysis is ready! You can review it by opening the Markdown file: [file_name](#hello-pdf) + You can also ask me any follow-up questions that you have or adjust any part by generating a revised analysis.`, + customRenderer: [ + { + type: 'blockquote', + events: { + click: (e: Event) => { + console.log('Hello!', e); + }, + }, + + children: [ + { + type: 'table', + children: [ + { + type: 'tr', + children: [ + { + type: 'td', + attributes: { + style: 'min-width: 30px; width: 30px;', + }, + children: [ + { + type: 'img', + attributes: { + src: `data:image/svg+xml;base64,${window.btoa(attachmentIcon)}`, + }, + }, + ], + }, + { + type: 'td', + children: [ + { + type: 'strong', + children: ['Refactor_analysis_[id] .pdf'], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], +}; + + +export const exampleDownloadFile: ChatItem = { + messageId: new Date().getTime().toString(), + type: ChatItemType.ANSWER, + canBeVoted: true, + body: `Your Refactor analysis is ready! You can review it by opening the Markdown file: [file_name](#hello-pdf) + You can also ask me any follow-up questions that you have or adjust any part by generating a revised analysis.`, + fileList: { + fileTreeTitle: 'Report', + rootFolderTitle: '', + filePaths: ['Refactor_analysis_[id] .pdf'], + }, +}; + +export const exampleInformationCard = ( + statusType: null | Status, + statusBody: string | null, + snap?: boolean, +): ChatItem => { + return { + messageId: generateUID(), + snapToTop: snap === true, + type: ChatItemType.ANSWER, + informationCard: { + title: 'Information card', + description: 'With a description below the title.', + icon: MynahIcons.BUG, + content: { + body: sampleList2 as string, + }, + status: + statusType === null || statusBody === null + ? {} + : { + status: statusType, + icon: + statusType === 'warning' + ? MynahIcons.WARNING + : statusType === 'error' + ? MynahIcons.ERROR + : MynahIcons.THUMBS_UP, + body: statusBody, + }, + }, + }; +}; + +export const exampleBorderedCard = (): ChatItem => { + return { + messageId: generateUID(), + type: ChatItemType.ANSWER, + border: true, + header: { + padding: true, + iconForegroundStatus: 'warning', + icon: MynahIcons.INFO, + body: '### /dev is going away soon!' + }, + body: `With agentic coding, you can now ask me any coding question directly in the chat instead of using /dev. + +You can ask me to do things like: +1. Create a project +2. Add a feature +3. Modify your files + +Try it out by typing your request in the chat!`, + }; +}; + +export const exampleConfirmationButtons: ChatItem = { + type: ChatItemType.ANSWER, + messageId: new Date().getTime().toString(), + body: 'This example shows some buttons with the `position` prop set to `outside`. Now we can use them to, for example, ask for confirmation! Does that make sense?', + buttons: [ + { + id: 'confirmation-buttons-cancel', + text: `Cancel`, + status: 'error', + icon: MynahIcons.CANCEL_CIRCLE, + position: 'outside', + }, + { + id: 'confirmation-buttons-confirm', + text: `Confirm`, + status: 'success', + icon: MynahIcons.OK_CIRCLED, + position: 'outside', + }, + ], +}; + +export const exampleButtons: ChatItem = { + type: ChatItemType.ANSWER, + messageId: new Date().getTime().toString(), + body: 'This is a card with actions inside!', + buttons: [ + { + text: 'With Icon', + id: 'action-1', + status: 'info', + icon: MynahIcons.CHAT, + }, + { + text: 'Default', + description: 'This has no status set!', + id: 'action-2', + }, + { + text: 'Disabled', + description: 'This is disabled for some reason!', + id: 'action-3', + disabled: true, + }, + { + text: 'Primary hover (with flash)', + fillState: 'hover', + id: 'action-3', + flash: 'infinite', + status: 'primary', + }, + { + text: 'Primary', + description: 'This is colored!', + id: 'action-3', + status: 'primary', + }, + { + text: 'Main hover (with flash)', + fillState: 'hover', + id: 'action-3', + flash: 'infinite', + icon: MynahIcons.PROGRESS, + status: 'main', + }, + { + text: 'Main', + description: 'This is more colored!', + id: 'action-3', + status: 'main', + }, + { + text: 'Clear', + description: 'This is clear!', + id: 'action-3', + status: 'clear', + }, + ], +}; + +export const exampleStatusButtons: ChatItem = { + type: ChatItemType.ANSWER, + messageId: new Date().getTime().toString(), + body: 'These are buttons with statuses', + buttons: [ + { + text: 'Proceed', + id: 'proceed', + icon: MynahIcons.OK, + status: 'success', + flash: 'infinite', + }, + { + text: 'Caution', + id: 'caution', + icon: MynahIcons.WARNING, + status: 'warning', + }, + { + text: 'Cancel', + id: 'cancel', + icon: MynahIcons.CANCEL, + status: 'error', + }, + { + text: 'Change Folder', + id: 'change-folder', + icon: MynahIcons.REFRESH, + status: 'info', + }, + { + text: 'Change Folder', + id: 'change-folder', + icon: MynahIcons.REFRESH, + status: 'info', + }, + + // External buttons + { + text: 'Proceed', + id: 'proceed', + fillState: 'hover', + position: 'outside', + icon: MynahIcons.OK, + status: 'success', + flash: 'infinite', + }, + { + text: 'Caution', + fillState: 'hover', + position: 'outside', + id: 'caution', + icon: MynahIcons.WARNING, + status: 'warning', + }, + { + text: 'Cancel', + fillState: 'hover', + position: 'outside', + id: 'cancel', + icon: MynahIcons.CANCEL, + status: 'error', + }, + { + text: 'Change Folder', + fillState: 'hover', + position: 'outside', + id: 'change-folder', + icon: MynahIcons.REFRESH, + status: 'info', + }, + ], +}; + +export const exampleVoteChatItem: ChatItem = { + messageId: new Date().getTime().toString(), + type: ChatItemType.ANSWER, + canBeVoted: true, + body: 'This chat item can be voted.', +}; + +export const sampleHeaderTypes: ChatItem[] = [ + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: { + name: 'javascript', + base64Svg: + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEyIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0zLjEyIDAuMjRINC44MlY1QzQuODIgNi4wMjY2NyA0LjU4IDYuNzczMzMgNC4xIDcuMjRDMy42NiA3LjY2NjY3IDMgNy44OCAyLjEyIDcuODhDMS45MiA3Ljg4IDEuNyA3Ljg2NjY3IDEuNDYgNy44NEMxLjIyIDcuOCAxLjAyNjY3IDcuNzQ2NjcgMC44OCA3LjY4TDEuMDYgNi4zMkMxLjMxMzMzIDYuNDQgMS42MDY2NyA2LjUgMS45NCA2LjVDMi4zMTMzMyA2LjUgMi41OTMzMyA2LjQgMi43OCA2LjJDMy4wMDY2NyA1Ljk2IDMuMTIgNS41NiAzLjEyIDVWMC4yNFpNNi4zMiA2QzYuNTYgNi4xMzMzMyA2Ljg0IDYuMjQ2NjcgNy4xNiA2LjM0QzcuNTIgNi40NDY2NyA3Ljg2IDYuNSA4LjE4IDYuNUM4LjU4IDYuNSA4Ljg4IDYuNDMzMzMgOS4wOCA2LjNDOS4yOCA2LjE1MzMzIDkuMzggNS45NTMzMyA5LjM4IDUuN0M5LjM4IDUuNDQ2NjcgOS4yODY2NyA1LjI0NjY3IDkuMSA1LjFDOC45MTMzMyA0Ljk0IDguNTg2NjcgNC43OCA4LjEyIDQuNjJDNi43NDY2NyA0LjE0IDYuMDYgMy4zOTMzMyA2LjA2IDIuMzhDNi4wNiAxLjcxMzMzIDYuMzEzMzMgMS4xNzMzMyA2LjgyIDAuNzU5OTk5QzcuMzQgMC4zMzMzMzMgOC4wNDY2NyAwLjEyIDguOTQgMC4xMkM5LjY0NjY3IDAuMTIgMTAuMjkzMyAwLjI0NjY2NyAxMC44OCAwLjVMMTAuNSAxLjg4TDEwLjM4IDEuODJDMTAuMTQgMS43MjY2NyA5Ljk0NjY3IDEuNjYgOS44IDEuNjJDOS41MiAxLjU0IDkuMjMzMzMgMS41IDguOTQgMS41QzguNTggMS41IDguMyAxLjU3MzMzIDguMSAxLjcyQzcuOTEzMzMgMS44NTMzMyA3LjgyIDIuMDMzMzMgNy44MiAyLjI2QzcuODIgMi40ODY2NyA3LjkyNjY3IDIuNjczMzMgOC4xNCAyLjgyQzguMyAyLjk0IDguNjQ2NjcgMy4xMDY2NyA5LjE4IDMuMzJDOS44NDY2NyAzLjU3MzMzIDEwLjMzMzMgMy44OCAxMC42NCA0LjI0QzEwLjk2IDQuNiAxMS4xMiA1LjA0IDExLjEyIDUuNTZDMTEuMTIgNi4yMjY2NyAxMC44NjY3IDYuNzY2NjcgMTAuMzYgNy4xOEM5LjgxMzMzIDcuNjQ2NjcgOS4wNDY2NyA3Ljg4IDguMDYgNy44OEM3LjY3MzMzIDcuODggNy4yNjY2NyA3LjgyNjY3IDYuODQgNy43MkM2LjUwNjY3IDcuNjUzMzMgNi4yMDY2NyA3LjU2IDUuOTQgNy40NEw2LjMyIDZaIiBmaWxsPSIjQ0JDQjQxIi8+Cjwvc3ZnPgo=', + }, + status: { + icon: MynahIcons.PROGRESS, + text: 'Working', + status: 'warning', + }, + buttons: [ + { + id: 'stop', + status: 'clear', + icon: MynahIcons.STOP, + }, + ], + fileList: { + fileTreeTitle: '', + filePaths: ['package.json'], + details: { + 'package.json': { + icon: null, + label: 'Creating', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + buttons: [ + { + id: 'undo-all', + status: 'clear', + position: 'outside', + keepCardAfterClick: false, + icon: MynahIcons.UNDO, + text: 'Undo all changes', + }, + ], + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: 'code-block', + status: { + icon: MynahIcons.OK, + text: 'Accepted', + status: 'success', + }, + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: ['package.json'], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + }, + + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + muted: true, + header: { + icon: 'code-block', + status: { + icon: MynahIcons.OK, + text: 'Accepted', + status: 'success', + }, + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: ['package.json'], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + }, + + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + muted: true, + header: { + icon: 'code-block', + status: { + icon: MynahIcons.CANCEL, + text: 'Rejected', + }, + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: ['package.json'], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + }, + + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + muted: true, + header: { + icon: 'code-block', + status: { + icon: MynahIcons.ERROR, + text: 'Error', + status: 'error', + description: 'There was an error while creating the file.', + }, + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: ['package.json'], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + }, + + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + messageId: generateUID(), + header: { + icon: 'code-block', + buttons: [ + { + icon: 'cancel', + status: 'clear', + id: 'reject-file-change-on-header-card', + }, + { + icon: 'ok', + status: 'clear', + id: 'accept-file-change-on-header-card', + }, + ], + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: ['package.json'], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + body: ` +\`\`\`bash +hello +\`\`\` +\`\`\`diff-typescript +const mynahUI = new MynahUI({ +tabs: { + 'tab-1': { + isSelected: true, + store: { + tabTitle: 'Chat', + chatItems: [ + { + type: ChatItemType.ANSWER, + body: 'Welcome to our chat!', + messageId: 'welcome-message' + }, + ], +- promptInputPlaceholder: 'Write your question', ++ promptInputPlaceholder: 'Type your question', + } + } +}, +- onChatPrompt: () => {}, ++ onChatPrompt: (tabId: string, prompt: ChatPrompt) => { ++ mynahUI.addChatItem(tabId, { ++ type: ChatItemType.PROMPT, ++ messageId: new Date().getTime().toString(), ++ body: prompt.escapedPrompt ++ }); ++ // call your genAI action ++ } +}); +\`\`\` + `, + codeBlockActions: { + copy: null, + 'insert-to-cursor': null, + }, + }, + + { + fullWidth: true, + padding: false, + type: ChatItemType.ANSWER, + header: { + icon: MynahIcons.SHELL, + body: 'Terminal command', + status: { + icon: MynahIcons.PROGRESS, + }, + buttons: [ + { + status: 'clear', + icon: MynahIcons.STOP, + id: 'stop-bash-command', + }, + ], + }, + body: ` +\`\`\`bash +mkdir -p src/ lalalaaaa +\`\`\` +`, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + { + fullWidth: true, + padding: false, + type: ChatItemType.ANSWER, + header: { + body: 'Shell', + status: { + position: 'left', + icon: MynahIcons.WARNING, + status: 'warning', + description: 'This command may cause\nsignificant data loss or damage.', + }, + buttons: [ + { + status: 'clear', + icon: 'play', + text: 'Run', + id: 'run-bash-command', + }, + { + status: 'dimmed-clear', + icon: 'cancel', + text: 'Reject', + id: 'reject-bash-command', + }, + ], + }, + body: ` +\`\`\`bash +mkdir -p src/ lalalaaaa +\`\`\` +`, + quickSettings: { + type: "select", + messageId: "1", + tabId: "hello", + description: '', + descriptionLink: { + id: "button-id", + destination: "Built-in", + text: 'More control, modify the commands', + }, + options: [ + { id: 'option1', label: 'Ask to Run', selected: true, value: 'Destructive' }, + { id: 'option2', label: 'Auto run', value: 'Destructive' }, + ], + onChange: (selectedOptions: any) => { + console.log('Selected options:', selectedOptions); + } + }, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + { + fullWidth: true, + padding: false, + type: ChatItemType.ANSWER, + header: { + icon: MynahIcons.CODE_BLOCK, + body: 'Terminal command', + buttons: [ + { + status: 'clear', + icon: 'play', + id: 'run-bash-command', + }, + ], + }, + body: ` +\`\`\`bash +mkdir -p src/ lalalaaaa +\`\`\` +`, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + + { + fullWidth: true, + padding: false, + type: ChatItemType.ANSWER, + header: { + body: 'Shell', + status: { + position: 'left', + icon: MynahIcons.WARNING, + status: 'warning', + description: 'This command may cause\nsignificant data loss or damage.', + }, + buttons: [ + { + status: 'clear', + icon: 'play', + text: 'Run', + id: 'run-bash-command', + }, + { + status: 'dimmed-clear', + icon: 'cancel', + text: 'Reject', + id: 'reject-bash-command', + }, + ], + }, + body: ` +\`\`\`bash +mkdir -p src/ lalalaaaa +\`\`\` +`, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + + { + type: ChatItemType.DIRECTIVE, + body: `Starting with a directive with normal text.`, + }, + + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + buttons: [ + { + icon: 'undo', + text: 'Undo', + status: 'clear', + id: 'undo-change', // Or whatever ID you have + }, + ], + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: ['maze_game.py'], + details: { + 'maze_game.py': { + description: 'Hello!', + icon: null, + changes: { + added: 131, + deleted: 0, + }, + }, + }, + }, + }, + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + status: 'error', + body: 'To avoid errors, do not make weird things in the system!', + header: { + icon: 'cancel', + iconForegroundStatus: 'error', + body: '##### Error on something!', + }, + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + // status: '', + body: 'To avoid warnings, do not make weird things in the system!', + header: { + status: { + icon: 'warning', + status: 'warning', + position: 'left', + description: 'There is an error!', + }, + body: '##### Warning on something!', + buttons: [ + { + id: 'accept-warning', + text: 'Accept', + status: 'clear', + icon: 'ok', + }, + ], + }, + }, + { + fullWidth: true, + padding: false, + type: ChatItemType.ANSWER, + wrapCodes: true, + header: { + icon: MynahIcons.CODE_BLOCK, + body: 'Terminal command', + buttons: [ + { + status: 'clear', + icon: 'play', + text: 'Run', + id: 'run-bash-command', + }, + { + status: 'dimmed-clear', + icon: 'cancel', + text: 'Reject', + id: 'cancel-bash-command', + }, + ], + }, + body: ` +\`\`\`bash +mkdir -p src/ lalalaaaa sad fbnsafsdaf sdakjfsd sadf asdkljf basdkjfh ksajhf kjsadhf dskjkj hasdklf askdjfh kj sadhfksdaf +\`\`\` +`, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: 'progress', + body: 'Reading', + fileList: { + filePaths: ['package.json', 'README.md'], + details: { + 'package.json': { + visibleName: 'package.json', + description: 'package.json' + }, + 'README.md': { + visibleName: 'README.md', + description: 'README.md' + } + }, + renderAsPills: true + } + }, + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: 'eye', + body: '5 files read', + fileList: { + filePaths: ['package.json', 'README.md', 'webpack.config.js', 'src/app.ts', 'src/components/Button/Button.tsx'], + details: { + 'package.json': { + visibleName: 'package.json', + description: 'package.json' + }, + 'README.md': { + visibleName: 'README.md', + description: 'README.md' + }, + 'webpack.config.js': { + visibleName: 'webpack.config.js', + description: 'webpack.config.js' + }, + 'src/app.ts': { + visibleName: 'app.ts', + description: 'src/app.ts' + }, + 'src/components/Button/Button.tsx': { + visibleName: 'Button.tsx', + description: 'src/components/Button/Button.tsx' + } + }, + renderAsPills: true + } + }, + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: 'progress', + body: 'Listing', + fileList: { + filePaths: ['src/components/ui', 'src/components/forms'], + details: { + 'src/components/ui': { + visibleName: 'ui', + description: 'src/components/ui', + clickable: false + }, + 'src/components/forms': { + visibleName: 'forms', + description: 'src/components/forms', + clickable: false + }, + }, + renderAsPills: true + } + } + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: 'check-list', + body: '5 directories listed', + fileList: { + filePaths: ['src/components/ui', 'src/components/forms', 'src/components/layout', 'src/utils/helpers', 'src/utils/validation'], + details: { + 'src/components/ui': { + visibleName: 'ui', + description: 'src/components/ui', + clickable: false + }, + 'src/components/forms': { + visibleName: 'forms', + description: 'src/components/forms', + clickable: false + }, + 'src/components/layout': { + visibleName: 'layout', + description: 'src/components/layout', + clickable: false + }, + 'src/utils/helpers': { + visibleName: 'helpers', + description: 'src/components/helpers', + clickable: false + }, + 'src/utils/validation': { + visibleName: 'validation', + description: 'src/components/validation', + clickable: false + }, + }, + renderAsPills: true + } + } + }, + { + type: ChatItemType.ANSWER, + fullWidth: true, + padding: false, + header: { + icon: 'search', + body: 'Searched for `*.md` in', + fileList: { + filePaths: ['src/docs'], + details: { + ['src/docs']: { + visibleName: 'docs', + description: 'src/docs', + clickable: false + } + }, + renderAsPills: true + }, + status: { + text: '5 results found' + } + }, + } +]; + + +export const mcpToolRunSampleCardInit:ChatItem = // Summary Card +{ + padding: false, + type: ChatItemType.ANSWER, + summary: { + content: { + padding: false, + wrapCodes: true, + header: { + icon: MynahIcons.TOOLS, + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: ['Running'], + details: { + 'Running': { + description: 'Work in progress!', + icon: null, + labelIcon: 'progress', + labelIconForegroundStatus: 'info', + label: 'Filesystem tool search-files' + }, + }, + }, + }, + }, + collapsedContent: [ + { + fullWidth: true, + padding: false, + muted: true, + wrapCodes: true, + header: { + body: 'Parameters', + }, + body: ` +\`\`\`json +{ +"query": "user:zakiyav", +"perPage": 100, +"page": 1 +} +\`\`\` + `, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + ], + }, +}; +export const mcpToolRunSampleCard:ChatItem = // Summary Card +{ + padding: false, + type: ChatItemType.ANSWER, + summary: { + content: { + padding: false, + wrapCodes: true, + header: { + icon: MynahIcons.TOOLS, + body: 'Ran Filesystem tool search-files', + fileList: null, + buttons: [ + { + status: 'clear', + icon: 'play', + text: 'Run', + id: 'run-bash-command', + }, + { + status: 'dimmed-clear', + icon: 'cancel', + text: 'Reject', + id: 'reject-bash-command', + }, + ], + }, + quickSettings: { + type: "select", + messageId: "1", + tabId: "hello", + description: '', + descriptionLink: { + id: "button-id", + destination: "Built-in", + text: 'Auto-approve settings', + }, + options: [ + { id: 'option1', label: 'Ask to Run', selected: true, value: 'Destructive' }, + { id: 'option2', label: 'Auto run', value: 'Destructive' }, + ], + onChange: (selectedOptions: any) => { + console.log('Selected options:', selectedOptions); + } + }, + }, + collapsedContent: [ + { + fullWidth: true, + padding: false, + muted: true, + wrapCodes: true, + header: { + body: 'Parameters', + }, + body: ` +\`\`\`json +{ +"query": "user:zakiyav", +"perPage": 100, +"page": 1 +} +\`\`\` + `, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + { + fullWidth: true, + padding: false, + muted: true, + wrapCodes: false, + header: { + body: 'Results', + }, + body: ` +\`\`\`json +{ + "total_count": 1, + "incomplete_results": false, + "items": [ + { + "id": 69836433, + "node_id": "MDasflJlcG9zaXRvcasdnk2OTgzN", + "name": "Repo1", + "full_name": "zakiyav/Repo1", + "private": true, + "owner": { + "login": "zakiyav", + "id": 5873805510, + "node_id": "MasDQgb6VjXNlcjQ5MzU1sfasMTA=", + "avatar_url": "https://avatars.githubus?v=4", + "url": "https://api.github.com/users/zakiyav", + "html_url": "https://github.com/zakiyav", + "type": "User" + } + "default_branch": "master" + } + ] +} +\`\`\` + `, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }, + ], + }, +}; + +export const sampleMCPList: DetailedList = { + selectable: 'clickable', + header: { + title: 'MCP Servers', + status: {}, + description: + 'Q automatically uses any MCP servers that have been added, so you don\'t have to add them as context. All MCPs are defaulted to "Ask before running".', + actions: [ + { + id: 'add-new-mcp', + icon: 'plus', + status: 'clear', + description: 'Add new MCP', + }, + { + id: 'refresh-mcp-list', + icon: 'refresh', + status: 'clear', + description: 'Refresh MCP servers', + }, + ], + }, + textDirection: 'row', + list: [ + { + groupName: 'Active', + children: [ + { + title: 'Built-in', + icon: 'ok-circled', + status: { + description: '8 available tools', + icon: 'tools', + text: '8', + }, + iconForegroundStatus: 'success', + actions: [ + { + id: 'open-mcp-xx', + icon: 'right-open', + }, + ], + }, + { + title: 'Filesystem', + icon: 'ok-circled', + status: { + icon: 'tools', + description: '26 available tools', + text: '26', + }, + iconForegroundStatus: 'success', + actions: [ + { + id: 'open-mcp-xx', + icon: 'right-open', + }, + ], + }, + { + title: 'Git', + icon: 'cancel-circle', + iconForegroundStatus: 'error', + description: 'Configuration is broken', + groupActions: false, + actions: [ + { + id: 'open-mcp-xx', + text: 'Fix configuration', + icon: 'pencil', + }, + { + id: 'open-mcp-xx', + disabled: true, + icon: 'right-open', + }, + ], + }, + { + title: 'Github', + icon: 'progress', + iconForegroundStatus: 'info', + groupActions: false, + actions: [ + { + id: 'open-mcp-xx', + icon: 'right-open', + }, + ], + }, + ], + }, + { + groupName: 'Disabled', + children: [ + { + title: 'Redis', + icon: 'block', + groupActions: false, + actions: [ + { + id: 'mcp-enable-tool', + icon: MynahIcons.OK, + text: 'Enable', + }, + { + id: 'mcp-delete-tool', + icon: MynahIcons.TRASH, + text: 'Delete', + confirmation: { + cancelButtonText: 'Cancel', + confirmButtonText: 'Delete', + title: 'Delete Filesystem MCP server', + description: + 'This configuration will be deleted and no longer available in Q. \n\n **This cannot be undone.**', + }, + }, + { + id: 'open-mcp-xx', + disabled: true, + icon: 'right-open', + }, + ], + }, + ], + }, + ], + filterOptions: [], + filterActions: [], +}; + +export const sampleMCPDetails = (title: string): DetailedList => { + return { + header: { + title: `MCP: ${title}`, + status: { + title: 'Detail of the issue', + icon: 'cancel-circle', + status: 'error', + }, + description: + 'Extend the capabilities of Q with [MCP servers](#). Q automatically uses any MCP server that has been added. All MCPs are defaulted to "Ask before running". [Learn more](#)', + actions: [ + { + icon: 'pencil', + text: 'Edit setup', + id: 'mcp-edit-setup', + }, + { + icon: 'ellipsis-h', + id: 'mcp-details-menu', + items: [ + { + id: 'mcp-disable-tool', + text: `Disable ${title}`, + icon: 'block', + }, + { + id: 'mcp-delete-tool', + confirmation: { + cancelButtonText: 'Cancel', + confirmButtonText: 'Delete', + title: 'Delete Filesystem MCP server', + description: + 'This configuration will be deleted and no longer available in Q. \n\n This cannot be undone.', + }, + text: `Delete ${title}`, + icon: 'trash', + }, + ], + }, + ], + }, + list: [], + filterActions: [ + { + id: 'cancel-mcp', + text: 'Cancel', + }, + { + id: 'save-mcp', + text: 'Save', + status: 'primary', + }, + ], + filterOptions: [ + { + type: 'select', + id: 'auto-approve', + title: 'Auto Approve', + options: [ + { + label: 'Yes', + value: 'yes', + }, + { + label: 'No', + value: 'no', + }, + ], + selectTooltip: "Permission for this tool is not configurable yet", + mandatory: true, + disabled: true, + hideMandatoryIcon: true, + boldTitle: true, + }, + { + type: 'select', + id: 'tool_name', + title: 'Tool Name', + value: 'alwaysAllow', + options: [ + { + label: 'Ask', + value: 'ask', + description: "Ask for your approval each time this tool is run" + }, + { + label: 'Always Allow', + value: 'alwaysAllow', + description: 'Always allow this tool to run without asking for approval' + }, + { + label: 'Deny', + value: 'deny', + description: 'Never run this tool' + }, + ], + boldTitle: true, + mandatory: true, + hideMandatoryIcon: true, + }, + { + type: 'select', + id: 'transport', + title: 'Transport', + options: [ + { + label: 'Yes', + value: 'yes', + }, + { + label: 'No', + value: 'no', + }, + ] + }, + { + type: 'textinput', + title: 'Command', + id: 'command', + }, + { + type: 'numericinput', + title: 'Timeout', + description: 'Seconds', + id: 'timeout', + }, + { // Add mandatory field + id: 'args-pillbox', + type: 'pillbox', + title: 'Arguments - pillbox', + placeholder: 'Type arguments and press Enter', + value: '-y,@modelcontextprotocol/server-filesystem,/Users/username/Desktop,/path/to/other/allowed/dir', + }, + { + id: 'args', + type: 'list', + title: 'Arguments', + mandatory: false, + items: [ + { + id: 'arg_key', + type: 'textinput', + }, + ], + value: [ + { + persistent: true, + value: { + arg_key: '-y', + }, + }, + { + value: { + arg_key: '@modelcontextprotocol/server-filesystem', + }, + }, + { + value: { + arg_key: '/Users/username/Desktop', + }, + }, + { + value: { + arg_key: '/path/to/other/allowed/dir', + }, + }, + ], + }, + { + id: 'env_variables', + type: 'list', + title: 'Environment variables', + items: [ + { + id: 'env_var_name', + title: 'Name', + type: 'textinput', + }, + { + id: 'env_var_value', + title: 'Value', + type: 'textinput', + }, + ], + value: [ + { + value: { + env_var_name: 'some_env', + env_var_value: 'AJSKJLE!@)(UD', + }, + }, + { + value: { + env_var_name: 'some_other_env', + env_var_value: '12kjlkj!dddaa', + }, + }, + ], + }, + ], + }; +}; + + +export const sampleRulesList: DetailedList = {selectable: 'clickable', list: [{children: [{id: 'README', icon: MynahIcons.CHECK_LIST, + description: 'README',actions: [{ id: 'README.md', icon: MynahIcons.OK, status: 'clear' }]}]}, + {groupName: '.amazonq/rules', childrenIndented: true, icon: MynahIcons.FOLDER , actions: [{ id: 'java-expert.md', icon: MynahIcons.OK, status: 'clear' }], children: [{id: 'java-expert.md', icon: MynahIcons.CHECK_LIST, + description: 'java-expert',actions: [{ id: 'java-expert.md', icon: MynahIcons.OK, status: 'clear' }]}]}]} \ No newline at end of file diff --git a/vendor/mynah-ui/example/src/samples/sample-diff-applied.md b/vendor/mynah-ui/example/src/samples/sample-diff-applied.md new file mode 100644 index 00000000..639cbef7 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-diff-applied.md @@ -0,0 +1,28 @@ +```typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + isSelected: true, + store: { + tabTitle: 'Chat', + chatItems: [ + { + type: ChatItemType.ANSWER, + body: 'Welcome to our chat!', + messageId: 'welcome-message' + }, + ], + promptInputPlaceholder: 'Type your question', + } + } + }, + onChatPrompt: (tabId: string, prompt: ChatPrompt) => { + mynahUI.addChatItem(tabId, { + type: ChatItemType.PROMPT, + messageId: new Date().getTime().toString(), + body: prompt.escapedPrompt + }); + // call your genAI action + } +}); +``` \ No newline at end of file diff --git a/vendor/mynah-ui/example/src/samples/sample-diff.md b/vendor/mynah-ui/example/src/samples/sample-diff.md new file mode 100644 index 00000000..1b6813f2 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-diff.md @@ -0,0 +1,34 @@ +### Refactoring process on `index.ts` + +Changes will be applied _between line 42 and 70_. + +```diff-typescript +const mynahUI = new MynahUI({ + tabs: { + 'tab-1': { + isSelected: true, + store: { + tabTitle: 'Chat', + chatItems: [ + { + type: ChatItemType.ANSWER, + body: 'Welcome to our chat!', + messageId: 'welcome-message' + }, + ], +- promptInputPlaceholder: 'Write your question', ++ promptInputPlaceholder: 'Type your question', + } + } + }, +- onChatPrompt: () => {}, ++ onChatPrompt: (tabId: string, prompt: ChatPrompt) => { ++ mynahUI.addChatItem(tabId, { ++ type: ChatItemType.PROMPT, ++ messageId: new Date().getTime().toString(), ++ body: prompt.escapedPrompt ++ }); ++ // call your genAI action ++ } +}); +``` \ No newline at end of file diff --git a/vendor/mynah-ui/example/src/samples/sample-list-0.md b/vendor/mynah-ui/example/src/samples/sample-list-0.md new file mode 100644 index 00000000..7ac8da41 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-list-0.md @@ -0,0 +1 @@ +This is a list below with some code and bolds inside \ No newline at end of file diff --git a/vendor/mynah-ui/example/src/samples/sample-list-1.md b/vendor/mynah-ui/example/src/samples/sample-list-1.md new file mode 100644 index 00000000..ecb20e78 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-list-1.md @@ -0,0 +1,6 @@ +This is a list below with some code and bolds inside + +- **Bold** Text with some `inline code`. +- Also with some code blocks ```const a = 5;``` + +End of the list. \ No newline at end of file diff --git a/vendor/mynah-ui/example/src/samples/sample-list-2.md b/vendor/mynah-ui/example/src/samples/sample-list-2.md new file mode 100644 index 00000000..9bbad813 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-list-2.md @@ -0,0 +1,12 @@ +This is a list below with some code and bolds inside + +- **Bold** Text with some `inline code`. +- Also with some code blocks ```const a = 5;``` + +End of the list. + +List with numbers. +1. **Item1** This is the first list item. +2. **Item2** This is the second list item. +3. **Item3** This is the third list item. +4. **Item4** This is the fourth list item. And it also has a [LINK](#) inside. \ No newline at end of file diff --git a/vendor/mynah-ui/example/src/samples/sample-list-3.md b/vendor/mynah-ui/example/src/samples/sample-list-3.md new file mode 100644 index 00000000..f8737032 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-list-3.md @@ -0,0 +1,28 @@ +This is a list below with some code and bolds inside + +- **Bold** Text with some `inline code`. +- Also with some code blocks ```const a = 5;``` + +End of the list. + +List with numbers. +1. **Item1** This is the first list item. +2. **Item2** This is the second list item. +3. **Item3** This is the third list item. +4. **Item4** This is the fourth list item. And it also has a [LINK](#) inside. + +Nested List with numbers. +1. **Item1** This is the first list item. + - Test 1 + - Test 2 + - Test 3 some `inline code example (code without pre)` and right after that we want to show a real code block: + ```javascript + this is a block code; + and also a new line; + and another line; + ``` + And after that we're still on the same 3rd level item. + +2. **Item2** This is the second list item. +3. **Item3** This is the third list item. +4. **Item4** This is the fourth list item. And it also has a [LINK](#) inside. \ No newline at end of file diff --git a/vendor/mynah-ui/example/src/samples/sample-list-4.md b/vendor/mynah-ui/example/src/samples/sample-list-4.md new file mode 100644 index 00000000..10024fc7 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-list-4.md @@ -0,0 +1,39 @@ +This is a list below with some code and bolds inside + +- **Bold** Text with some `inline code`. +- Also with some code blocks ```const a = 5;``` + +End of the list. + +List with numbers. +1. **Item1** This is the first list item. +2. **Item2** This is the second list item. +3. **Item3** This is the third list item. +4. **Item4** This is the fourth list item. And it also has a [LINK](#) inside. + +Nested List with numbers. +1. **Item1** This is the first list item. + - Test 1 + - Test 2 + - Test 3 some `inline code example (code without pre)` and right after that we want to show a real code block: + ```javascript + this is a block code; + and also a new line; + and another line; + ``` + And after that we're still on the same 3rd level item. + +2. **Item2** This is the second list item. +3. **Item3** This is the third list item. +4. **Item4** This is the fourth list item. And it also has a [LINK](#) inside. + +To create a code **block** in Markdown, you would use the triple backticks (\`\`\`) with following syntax: + +> \`\`\` +> let a = 5; +> console.log(a); +> a = a + 5; +> console.log(a); +> \`\`\` + +The asterisk `*` creates an unordered list item, and the double asterisks `**text**` wrap the text you want to make bold. [[1]](https://aasiyaonize.hashnode.dev/markdown-and-restructured) diff --git a/vendor/mynah-ui/example/src/samples/sample-table.md b/vendor/mynah-ui/example/src/samples/sample-table.md new file mode 100644 index 00000000..15a3bd36 --- /dev/null +++ b/vendor/mynah-ui/example/src/samples/sample-table.md @@ -0,0 +1,10 @@ +# Project Features Overview + +| Feature | Description | Priority | Status | +|:------------------|--------------------------------------------------|:----------:|-------------:| +| User Authentication | Implement secure login and registration | High | In Progress | +| Dashboard | Create a user dashboard with key metrics | Medium | Planned | +| Reporting | Generate monthly performance reports | Low | Not Started | +| Notifications | Add email and push notifications for updates | Medium | In Progress | +| API Integration | Integrate third-party APIs for data enrichment | High | Completed | +| User Feedback | Collect and analyze user feedback | Low | Not Started | diff --git a/vendor/mynah-ui/example/src/styles/styles.scss b/vendor/mynah-ui/example/src/styles/styles.scss new file mode 100644 index 00000000..7fba707f --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/styles.scss @@ -0,0 +1,581 @@ +@import 'variables'; + +// Since mynah-ui css custom properties are mapped to VSCode theme ones by default +// here are some sample VSCode themes applied. +@import 'themes/light+.scss'; +@import 'themes/light+tweaked.scss'; +@import 'themes/light-orange.scss'; +@import 'themes/light-quiet.scss'; +@import 'themes/light-solarized.scss'; +@import 'themes/dark-plus.scss'; +@import 'themes/dark+tweaked.scss'; +@import 'themes/dark-abyss.scss'; +@import 'themes/dark-ayu-mirage.scss'; +@import 'themes/dark-dracula.scss'; +@import 'themes/dark-solarized.scss'; + +html, +body { + width: 100vw; + height: 100%; + margin: 0; + padding: 0; + display: block; + overflow: hidden; + background-color: var(--mynah-color-bg); + color: var(--mynah-color-text-default); +} +.mynah-extension-showcase-grid { + &:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--mynah-color-syntax-bg); + z-index: var(--mynah-z-0); + opacity: 0.25; + pointer-events: none; + } + transition: all 850ms cubic-bezier(0.25, 1, 0, 1); + display: grid; + grid-template-rows: auto 1fr; + grid-template-columns: 2fr 3fr 2fr; + height: 100%; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow: hidden; + gap: var(--mynah-sizing-5); + padding: var(--mynah-sizing-5); + position: relative; + + > * { + overflow: hidden; + padding: var(--mynah-sizing-5); + box-shadow: 0 5px 20px -5px rgba(0, 0, 0, 0.15); + border-radius: var(--mynah-card-radius); + background-color: var(--mynah-card-bg) !important; + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + z-index: var(--mynah-z-1); + &:not(#header-var):before { + padding-bottom: var(--mynah-sizing-5); + font-size: 130%; + font-weight: 800; + opacity: 0.75; + } + } + + #header-bar { + grid-row-start: 1; + grid-column-start: 1; + + grid-row-end: 1; + grid-column-end: -1; + + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; + gap: var(--mynah-sizing-2); + padding: var(--mynah-sizing-2); + + label { + cursor: pointer; + padding: var(--mynah-sizing-3) var(--mynah-sizing-2); + border-radius: var(--mynah-input-radius); + display: inline-flex; + justify-content: center; + align-items: center; + gap: var(--mynah-sizing-2); + &:before { + transition: all 850ms cubic-bezier(0.25, 1, 0, 1); + color: var(--mynah-color-button-reverse); + content: ''; + display: block; + position: absolute; + right: var(--mynah-sizing-1); + width: var(--mynah-sizing-6); + height: var(--mynah-sizing-6); + border-radius: var(--mynah-sizing-3); + background-color: var(--mynah-color-status-success); + } + &:after { + transition: all 850ms cubic-bezier(0.25, 1, 0, 1); + background-color: var(--mynah-color-button-reverse); + content: ''; + display: block; + -webkit-mask-image: var(--mynah-ui-icon-ok); + mask-image: var(--mynah-ui-icon-ok); + -webkit-mask-size: 100%; + mask-size: 100%; + -webkit-mask-position: center center; + mask-position: center center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + width: var(--mynah-sizing-4); + height: var(--mynah-sizing-4); + } + } + } + + #theme-editor { + &:before { + content: 'Theme Builder'; + } + grid-row-start: 2; + grid-column-start: 1; + + grid-row-end: 2; + grid-column-end: 1; + overflow: hidden; + + display: flex; + flex-flow: column nowrap; + } + + #mynah-ui-panel { + &:before { + content: 'Mynah UI (Example)'; + } + grid-row-start: 2; + grid-column-start: 2; + grid-row-end: 2; + grid-column-end: 3; + position: relative; + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: stretch; + + > #amzn-mynah-website-wrapper { + max-width: var(--mynah-max-width); + box-shadow: 0 5px 20px -15px rgba(0, 0, 0, 0.5); + border-radius: var(--mynah-input-radius); + background-color: var(--mynah-color-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + position: relative; + display: flex; + flex-flow: row nowrap; + flex: 1; + height: 100%; + box-sizing: border-box; + overflow: hidden; + margin: 0 auto; + width: 100%; + } + } + + #console { + &:before { + content: 'Console'; + } + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + display: flex; + flex-flow: column nowrap; + box-sizing: border-box; + min-height: 100%; + max-height: 100%; + max-height: 80px; + overflow: hidden; + grid-row-start: 2; + grid-column-start: 3; + grid-row-end: 2; + grid-column-end: 3; + z-index: 9; + + #console-content { + flex: 1; + overflow-x: hidden; + overflow-y: auto; + position: relative; + font-family: monospace; + display: flex; + font-size: 80%; + flex-flow: column-reverse nowrap; + gap: var(--mynah-sizing-3); + color: var(--mynah-color-syntax-code); + background-color: var(--mynah-card-bg); + padding: var(--mynah-sizing-4); + + > p { + margin-block-start: 0; + margin-block-end: 0; + position: relative; + padding-left: var(--mynah-sizing-6); + + &::first-letter { + text-transform: capitalize; + } + + &:before { + content: '>>'; + color: var(--mynah-color-syntax-attr); + position: absolute; + left: 0; + top: 0; + } + + b { + color: var(--mynah-color-syntax-property); + font-weight: 500; + } + } + } + } +} + +.mynah-ui-example-input-main-wrapper { + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-5); + overflow: hidden; + flex: 1; + overflow-x: hidden; + overflow-y: auto; + color: var(--mynah-color-text-default); + > .mynah-ui-example-input-items-wrapper { + order: 100; + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-5); + overflow: hidden; + flex-shrink: 0; + } + > h1 { + margin: 0; + } + > p { + order: 10; + } +} + +.mynah-ui-example-input-buttons-wrapper { + display: flex; + flex-flow: row wrap; + gap: var(--mynah-sizing-2); + overflow: hidden; + padding-bottom: var(--mynah-sizing-5); + flex-shrink: 0; + > button > span { + white-space: nowrap; + } + > .config-operation { + &.hidden { + opacity: 0; + visibility: hidden; + display: none; + } + } +} + +.mynah-ui-example-input { + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-3); + &-category { + &-sizing { + order: 10; + } + &-border-style { + order: 20; + } + &-font-size { + order: 30; + } + &-font-family { + order: 40; + } + &-text-color { + order: 50; + } + &-syntax-color { + order: 60; + } + &-status-color { + order: 70; + } + &-background-color { + order: 80; + } + &-shadow { + order: 90; + } + &-radius { + order: 100; + } + &-transition { + order: 110; + } + &-other { + order: 1000; + } + } + > h1 { + padding-top: var(--mynah-sizing-6); + margin: 0; + text-transform: capitalize; + } + & > &-title-wrapper { + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-1); + h4 { + margin: 0; + text-transform: capitalize; + } + span { + font-style: italic; + font-size: 90%; + color: var(--mynah-color-text-weak); + } + } + & > &-wrapper { + display: flex; + flex-flow: row nowrap; + gap: var(--mynah-sizing-2); + position: relative; + &:has(select) { + > select { + padding-right: var(--mynah-sizing-5); + } + &:after { + content: ''; + -webkit-mask-image: var(--mynah-ui-icon-down-open); + mask-image: var(--mynah-ui-icon-down-open); + background-color: currentColor; + opacity: 0.5; + -webkit-mask-size: 100%; + mask-size: 100%; + -webkit-mask-position: center center; + mask-position: center center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + position: absolute; + right: var(--mynah-sizing-3); + top: 50%; + margin-top: calc(-1 * var(--mynah-sizing-2)); + width: var(--mynah-sizing-4); + height: var(--mynah-sizing-4); + } + } + > input[type='text'], + > input[type='number'] { + flex: 1; + } + > select { + appearance: none; + min-width: 70px; + cursor: pointer; + &#theme-selector { + max-width: 140px; + } + } + + > small, + > input, + > select { + &[type='color'], + &[type='range'] { + padding: var(--mynah-sizing-1); + width: 100px; + min-width: 100px; + max-width: 100px; + outline: none; + } + + background-color: var(--mynah-card-bg); + color: var(--mynah-color-text-input); + border-radius: var(--mynah-input-radius); + padding: var(--mynah-sizing-3); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + } + + > small { + border: none; + padding: 0 !important; + } + + > input[type='range'] { + border: none; + -webkit-appearance: none; + appearance: none; + background: transparent; + cursor: pointer; + + &:focus { + outline: none; + } + + &::-webkit-slider-runnable-track { + background-color: var(--mynah-color-border-default); + border-radius: 0.5rem; + height: var(--mynah-sizing-2); + } + + &::-webkit-slider-thumb { + outline: none; + -webkit-appearance: none; + appearance: none; + margin-top: calc(-1 * var(--mynah-sizing-2)); + background-color: var(--mynah-color-button); + height: var(--mynah-sizing-6); + width: var(--mynah-sizing-6); + border-radius: var(--mynah-sizing-3); + } + } + } +} + +.ver-div { + height: 30px; + border: var(--mynah-border-width) solid var(--mynah-color-border-default); +} + +input.hidden { + // visibility: hidden; + display: none; + opacity: 0; +} + +@media only screen and (max-width: 1280px) { + #theme-editor-enabled, + label[for='theme-editor-enabled'] { + display: none !important; + } + .mynah-extension-showcase-grid { + grid-template-columns: 4fr 3fr !important; + + #theme-editor { + display: none; + visibility: hidden; + } + + #mynah-ui-panel { + grid-column-start: 1; + grid-column-end: 2; + } + + #console { + grid-column-start: 2; + grid-column-end: 2; + } + } +} + +@media only screen and (max-width: 860px) { + #console-enabled, + label[for='console-enabled'] { + display: none !important; + } + .mynah-extension-showcase-grid { + grid-template-columns: 100% !important; + padding: 0; + gap: 0; + + #header-bar { + padding: var(--mynah-sizing-2); + box-shadow: none; + border-radius: 0; + border: none; + } + + #theme-editor { + display: none; + } + + #mynah-ui-panel { + #amzn-mynah-website-wrapper { + border-radius: 0; + border: none; + .mynah-nav-tabs-wrapper { + > .mynah-tabs-container > span:first-child > label { + border-top-left-radius: initial; + } + } + } + grid-column-start: 1; + grid-column-end: 1; + border-radius: 0; + padding: 0; + border-left: none; + border-right: none; + border-bottom: none; + &:before { + display: none; + } + } + + #console { + display: none; + } + } +} + +html:not([theme^='base']) { + label[for='theme-editor-enabled'] { + display: none !important; + } +} +html:not([theme^='base']) .mynah-extension-showcase-grid, +#theme-editor-enabled:not(:checked) ~ .mynah-extension-showcase-grid { + grid-template-columns: 1fr 2fr 2fr; + #theme-editor { + display: none; + visibility: hidden; + } + + #mynah-ui-panel { + grid-column-start: 1; + } + + #header-bar { + label[for='theme-editor-enabled'] { + &:before { + background-color: var(--mynah-color-text-weak); + } + &:after { + -webkit-mask-image: var(--mynah-ui-icon-minus); + mask-image: var(--mynah-ui-icon-minus); + } + } + } +} +#console-enabled:not(:checked) { + & ~ .mynah-extension-showcase-grid { + grid-template-columns: 2fr 2fr 1fr; + #console { + display: none; + visibility: hidden; + } + + #mynah-ui-panel { + grid-column-end: -1; + } + + #header-bar { + label[for='console-enabled'] { + &:before { + background-color: var(--mynah-color-text-weak); + } + &:after { + -webkit-mask-image: var(--mynah-ui-icon-minus); + mask-image: var(--mynah-ui-icon-minus); + } + } + } + } +} + +html:not([theme^='base']) #console-enabled:not(:checked) ~ .mynah-extension-showcase-grid, +#theme-editor-enabled:not(:checked) + #console-enabled:not(:checked) ~ .mynah-extension-showcase-grid { + #console { + display: none; + visibility: hidden; + } + + #mynah-ui-panel { + grid-column-end: -1; + } +} diff --git a/vendor/mynah-ui/example/src/styles/themes/dark+tweaked.scss b/vendor/mynah-ui/example/src/styles/themes/dark+tweaked.scss new file mode 100644 index 00000000..abb20071 --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/dark+tweaked.scss @@ -0,0 +1,825 @@ +html[theme='dark+tweaked']:root { + font-size: 13px !important; + --text-link-decoration: none; + --vscode-font-family: -apple-system, BlinkMacSystemFont, sans-serif; + --vscode-font-weight: normal; + --vscode-font-size: 13px; + --vscode-editor-font-family: Menlo, Monaco, 'Courier New', monospace; + --vscode-editor-font-weight: normal; + --vscode-editor-font-size: 12px; + --vscode-foreground: #cccccc; + --vscode-disabledForeground: rgba(204, 204, 204, 0.5); + --vscode-errorForeground: #f48771; + --vscode-descriptionForeground: rgba(204, 204, 204, 0.7); + --vscode-icon-foreground: rgba(255, 255, 255, 0.27); + --vscode-focusBorder: #007fd4; + --vscode-textLink-foreground: #3794ff; + --vscode-textLink-activeForeground: #3794ff; + --vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18); + --vscode-textPreformat-foreground: #d7ba7d; + --vscode-textPreformat-background: rgba(255, 255, 255, 0.1); + --vscode-textBlockQuote-background: #222222; + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4); + --vscode-sash-hoverBorder: #007fd4; + --vscode-badge-background: #4d4d4d; + --vscode-badge-foreground: #ffffff; + --vscode-activityWarningBadge-foreground: #000000; + --vscode-activityWarningBadge-background: #cca700; + --vscode-activityErrorBadge-foreground: #000000; + --vscode-activityErrorBadge-background: #f14c4c; + --vscode-scrollbar-shadow: rgba(0, 0, 0, 0.13); + --vscode-scrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-progressBar-background: #0e70c0; + --vscode-chart-line: #236b8e; + --vscode-chart-axis: rgba(191, 191, 191, 0.4); + --vscode-chart-guide: rgba(191, 191, 191, 0.2); + --vscode-editor-background: #1e1e1e; + --vscode-editor-foreground: #d4d4d4; + --vscode-editorStickyScroll-background: #1e1e1e; + --vscode-editorStickyScrollHover-background: #2a2d2e; + --vscode-editorStickyScroll-shadow: rgba(0, 0, 0, 0.13); + --vscode-editorWidget-background: #252526; + --vscode-editorWidget-foreground: #cccccc; + --vscode-editorWidget-border: #454545; + --vscode-editorError-foreground: #f14c4c; + --vscode-editorWarning-foreground: #cca700; + --vscode-editorInfo-foreground: #3794ff; + --vscode-editorHint-foreground: rgba(238, 238, 238, 0.7); + --vscode-editorLink-activeForeground: #4e94ce; + --vscode-editor-selectionBackground: #264f78; + --vscode-editor-inactiveSelectionBackground: #3a3d41; + --vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 0.15); + --vscode-editor-compositionBorder: #ffffff; + --vscode-editor-findMatchBackground: #515c6a; + --vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4); + --vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25); + --vscode-editorHoverWidget-background: #252526; + --vscode-editorHoverWidget-foreground: #cccccc; + --vscode-editorHoverWidget-border: #454545; + --vscode-editorHoverWidget-statusBarBackground: #2c2c2d; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(77, 77, 77, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(77, 77, 77, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(77, 77, 77, 0.1); + --vscode-editorLightBulb-foreground: #ffcc00; + --vscode-editorLightBulbAutoFix-foreground: #75beff; + --vscode-editorLightBulbAi-foreground: #ffcc00; + --vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, 0.3); + --vscode-editor-snippetFinalTabstopHighlightBorder: #525252; + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.2); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(204, 204, 204, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #1b1b1b; + --vscode-diffEditor-unchangedRegionForeground: #cccccc; + --vscode-diffEditor-unchangedCodeBackground: rgba(116, 116, 116, 0.16); + --vscode-widget-shadow: rgba(0, 0, 0, 0.13); + --vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31); + --vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31); + --vscode-breadcrumb-foreground: rgba(204, 204, 204, 0.8); + --vscode-breadcrumb-background: #20252b; + --vscode-breadcrumb-focusForeground: #e0e0e0; + --vscode-breadcrumb-activeSelectionForeground: #e0e0e0; + --vscode-breadcrumbPicker-background: #252526; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-problemsErrorIcon-foreground: #f14c4c; + --vscode-problemsWarningIcon-foreground: #cca700; + --vscode-problemsInfoIcon-foreground: #3794ff; + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #676767; + --vscode-minimap-selectionHighlight: #264f78; + --vscode-minimap-infoHighlight: #3794ff; + --vscode-minimap-warningHighlight: #cca700; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(121, 121, 121, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(191, 191, 191, 0.2); + --vscode-charts-foreground: #cccccc; + --vscode-charts-lines: rgba(204, 204, 204, 0.5); + --vscode-charts-red: #f14c4c; + --vscode-charts-blue: #3794ff; + --vscode-charts-yellow: #cca700; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #89d185; + --vscode-charts-purple: #b180d7; + --vscode-input-background: #3c3c3c; + --vscode-input-foreground: #cccccc; + --vscode-inputOption-activeBorder: #007acc; + --vscode-inputOption-hoverBackground: rgba(90, 93, 94, 0.5); + --vscode-inputOption-activeBackground: rgba(0, 127, 212, 0.4); + --vscode-inputOption-activeForeground: #ffffff; + --vscode-input-placeholderForeground: rgba(204, 204, 204, 0.5); + --vscode-inputValidation-infoBackground: #063b49; + --vscode-inputValidation-infoBorder: #007acc; + --vscode-inputValidation-warningBackground: #352a05; + --vscode-inputValidation-warningBorder: #b89500; + --vscode-inputValidation-errorBackground: #5a1d1d; + --vscode-inputValidation-errorBorder: #be1100; + --vscode-dropdown-background: #3c3c3c; + --vscode-dropdown-foreground: #f0f0f0; + --vscode-dropdown-border: #3c3c3c; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #0e639c; + --vscode-button-hoverBackground: #1177bb; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #3a3d41; + --vscode-button-secondaryHoverBackground: #45494e; + --vscode-radio-activeForeground: #ffffff; + --vscode-radio-activeBackground: rgba(0, 127, 212, 0.4); + --vscode-radio-activeBorder: #007acc; + --vscode-radio-inactiveBorder: rgba(255, 255, 255, 0.2); + --vscode-radio-inactiveHoverBackground: rgba(90, 93, 94, 0.5); + --vscode-checkbox-background: #3c3c3c; + --vscode-checkbox-selectBackground: #252526; + --vscode-checkbox-foreground: #f0f0f0; + --vscode-checkbox-border: #3c3c3c; + --vscode-checkbox-selectBorder: rgba(255, 255, 255, 0.27); + --vscode-keybindingLabel-background: rgba(128, 128, 128, 0.17); + --vscode-keybindingLabel-foreground: #cccccc; + --vscode-keybindingLabel-border: rgba(51, 51, 51, 0.6); + --vscode-keybindingLabel-bottomBorder: rgba(68, 68, 68, 0.6); + --vscode-list-focusOutline: #007fd4; + --vscode-list-activeSelectionBackground: #04395e; + --vscode-list-activeSelectionForeground: #ffffff; + --vscode-list-inactiveSelectionBackground: #37373d; + --vscode-list-hoverBackground: #2a2d2e; + --vscode-list-dropBackground: #383b3d; + --vscode-list-dropBetweenBackground: rgba(255, 255, 255, 0.27); + --vscode-list-highlightForeground: #2aaaff; + --vscode-list-focusHighlightForeground: #2aaaff; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #f88070; + --vscode-list-warningForeground: #cca700; + --vscode-listFilterWidget-background: #252526; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.13); + --vscode-list-filterMatchBackground: rgba(234, 92, 0, 0.33); + --vscode-list-deemphasizedForeground: #8c8c8c; + --vscode-tree-indentGuidesStroke: rgba(255, 255, 255, 0.07); + --vscode-tree-inactiveIndentGuidesStroke: rgba(255, 255, 255, 0.03); + --vscode-tree-tableColumnsBorder: rgba(204, 204, 204, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(204, 204, 204, 0.04); + --vscode-editorActionList-background: #252526; + --vscode-editorActionList-foreground: #cccccc; + --vscode-editorActionList-focusForeground: #ffffff; + --vscode-editorActionList-focusBackground: #04395e; + --vscode-menu-foreground: #f0f0f0; + --vscode-menu-background: #3c3c3c; + --vscode-menu-selectionForeground: #ffffff; + --vscode-menu-selectionBackground: #04395e; + --vscode-menu-separatorBackground: #606060; + --vscode-quickInput-background: #252526; + --vscode-quickInput-foreground: #cccccc; + --vscode-quickInputTitle-background: rgba(255, 255, 255, 0.1); + --vscode-pickerGroup-foreground: #3794ff; + --vscode-pickerGroup-border: #3f3f46; + --vscode-quickInputList-focusForeground: #ffffff; + --vscode-quickInputList-focusBackground: #04395e; + --vscode-search-resultsInfoForeground: rgba(204, 204, 204, 0.65); + --vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 0.22); + --vscode-editor-lineHighlightBackground: rgba(28, 46, 62, 0.4); + --vscode-editor-lineHighlightBorder: rgba(0, 0, 0, 0); + --vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editorCursor-foreground: #cfa738; + --vscode-editorCursor-background: #57430e; + --vscode-editorMultiCursor-primary\.foreground: #cfa738; + --vscode-editorMultiCursor-primary\.background: #57430e; + --vscode-editorMultiCursor-secondary\.foreground: #cfa738; + --vscode-editorMultiCursor-secondary\.background: #57430e; + --vscode-editorWhitespace-foreground: rgba(227, 228, 226, 0.16); + --vscode-editorLineNumber-foreground: #3a3a3a; + --vscode-editorIndentGuide-background: rgba(255, 255, 255, 0.05); + --vscode-editorIndentGuide-activeBackground: rgba(255, 255, 255, 0.2); + --vscode-editorIndentGuide-background1: rgba(255, 255, 255, 0.05); + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: rgba(255, 255, 255, 0.2); + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #c6c6c6; + --vscode-editorLineNumber-activeForeground: #afafaf; + --vscode-editorRuler-foreground: rgba(255, 255, 255, 0.07); + --vscode-editorCodeLens-foreground: #999999; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #888888; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #1e1e1e; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67); + --vscode-editorGhostText-foreground: rgba(255, 255, 255, 0.34); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #cca700; + --vscode-editorOverviewRuler-infoForeground: #3794ff; + --vscode-editorBracketHighlight-foreground1: #ffd700; + --vscode-editorBracketHighlight-foreground2: #da70d6; + --vscode-editorBracketHighlight-foreground3: #179fff; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #cca700; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-diffEditor-unchangedRegionShadow: #000000; + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-actionBar-toggledBackground: rgba(0, 127, 212, 0.4); + --vscode-symbolIcon-arrayForeground: #cccccc; + --vscode-symbolIcon-booleanForeground: #cccccc; + --vscode-symbolIcon-classForeground: #ee9d28; + --vscode-symbolIcon-colorForeground: #cccccc; + --vscode-symbolIcon-constantForeground: #cccccc; + --vscode-symbolIcon-constructorForeground: #b180d7; + --vscode-symbolIcon-enumeratorForeground: #ee9d28; + --vscode-symbolIcon-enumeratorMemberForeground: #75beff; + --vscode-symbolIcon-eventForeground: #ee9d28; + --vscode-symbolIcon-fieldForeground: #75beff; + --vscode-symbolIcon-fileForeground: #cccccc; + --vscode-symbolIcon-folderForeground: #cccccc; + --vscode-symbolIcon-functionForeground: #b180d7; + --vscode-symbolIcon-interfaceForeground: #75beff; + --vscode-symbolIcon-keyForeground: #cccccc; + --vscode-symbolIcon-keywordForeground: #cccccc; + --vscode-symbolIcon-methodForeground: #b180d7; + --vscode-symbolIcon-moduleForeground: #cccccc; + --vscode-symbolIcon-namespaceForeground: #cccccc; + --vscode-symbolIcon-nullForeground: #cccccc; + --vscode-symbolIcon-numberForeground: #cccccc; + --vscode-symbolIcon-objectForeground: #cccccc; + --vscode-symbolIcon-operatorForeground: #cccccc; + --vscode-symbolIcon-packageForeground: #cccccc; + --vscode-symbolIcon-propertyForeground: #cccccc; + --vscode-symbolIcon-referenceForeground: #cccccc; + --vscode-symbolIcon-snippetForeground: #cccccc; + --vscode-symbolIcon-stringForeground: #cccccc; + --vscode-symbolIcon-structForeground: #cccccc; + --vscode-symbolIcon-textForeground: #cccccc; + --vscode-symbolIcon-typeParameterForeground: #cccccc; + --vscode-symbolIcon-unitForeground: #cccccc; + --vscode-symbolIcon-variableForeground: #75beff; + --vscode-peekViewTitle-background: #007acc; + --vscode-peekViewTitleLabel-foreground: #ffffff; + --vscode-peekViewTitleDescription-foreground: rgba(204, 204, 204, 0.7); + --vscode-peekView-border: rgba(0, 122, 204, 0); + --vscode-peekViewResult-background: #1b1b1b; + --vscode-peekViewResult-lineForeground: #bbbbbb; + --vscode-peekViewResult-fileForeground: #ffffff; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #ffffff; + --vscode-peekViewEditor-background: #181818; + --vscode-peekViewEditorGutter-background: #1b1b1b; + --vscode-peekViewEditorStickyScroll-background: #181818; + --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3); + --vscode-peekViewEditor-matchHighlightBackground: rgba(255, 143, 0, 0.6); + --vscode-editor-foldBackground: rgba(38, 79, 120, 0.3); + --vscode-editor-foldPlaceholderForeground: #808080; + --vscode-editorGutter-foldingControlForeground: rgba(255, 255, 255, 0.27); + --vscode-editorSuggestWidget-background: #252526; + --vscode-editorSuggestWidget-border: #454545; + --vscode-editorSuggestWidget-foreground: #d4d4d4; + --vscode-editorSuggestWidget-selectedForeground: #ffffff; + --vscode-editorSuggestWidget-selectedBackground: #04395e; + --vscode-editorSuggestWidget-highlightForeground: #2aaaff; + --vscode-editorSuggestWidget-focusHighlightForeground: #2aaaff; + --vscode-editorSuggestWidgetStatus-foreground: rgba(212, 212, 212, 0.5); + --vscode-inlineEdit-originalBackground: rgba(255, 0, 0, 0.04); + --vscode-inlineEdit-modifiedBackground: rgba(156, 204, 44, 0.06); + --vscode-inlineEdit-originalChangedLineBackground: rgba(255, 0, 0, 0.16); + --vscode-inlineEdit-originalChangedTextBackground: rgba(255, 0, 0, 0.16); + --vscode-inlineEdit-modifiedChangedLineBackground: rgba(155, 185, 85, 0.14); + --vscode-inlineEdit-modifiedChangedTextBackground: rgba(156, 204, 44, 0.14); + --vscode-inlineEdit-gutterIndicator\.primaryForeground: #ffffff; + --vscode-inlineEdit-gutterIndicator\.primaryBorder: #0e639c; + --vscode-inlineEdit-gutterIndicator\.primaryBackground: rgba(14, 99, 156, 0.4); + --vscode-inlineEdit-gutterIndicator\.secondaryForeground: #ffffff; + --vscode-inlineEdit-gutterIndicator\.secondaryBorder: #3a3d41; + --vscode-inlineEdit-gutterIndicator\.secondaryBackground: #3a3d41; + --vscode-inlineEdit-gutterIndicator\.successfulForeground: #ffffff; + --vscode-inlineEdit-gutterIndicator\.successfulBorder: #0e639c; + --vscode-inlineEdit-gutterIndicator\.successfulBackground: #0e639c; + --vscode-inlineEdit-gutterIndicator\.background: rgba(30, 30, 30, 0.5); + --vscode-inlineEdit-originalBorder: rgba(255, 0, 0, 0.2); + --vscode-inlineEdit-modifiedBorder: rgba(156, 204, 44, 0.2); + --vscode-inlineEdit-tabWillAcceptModifiedBorder: rgba(156, 204, 44, 0.2); + --vscode-inlineEdit-tabWillAcceptOriginalBorder: rgba(255, 0, 0, 0.2); + --vscode-editorMarkerNavigationError-background: #f14c4c; + --vscode-editorMarkerNavigationError-headerBackground: rgba(241, 76, 76, 0.1); + --vscode-editorMarkerNavigationWarning-background: #cca700; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(204, 167, 0, 0.1); + --vscode-editorMarkerNavigationInfo-background: #3794ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, 0.1); + --vscode-editorMarkerNavigation-background: #1e1e1e; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.72); + --vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, 0.72); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.72); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-editorHoverWidget-highlightForeground: #2aaaff; + --vscode-editor-placeholder\.foreground: rgba(255, 255, 255, 0.34); + --vscode-tab-activeBackground: #20252b; + --vscode-tab-unfocusedActiveBackground: #20252b; + --vscode-tab-inactiveBackground: #1e1e1e; + --vscode-tab-unfocusedInactiveBackground: #1e1e1e; + --vscode-tab-activeForeground: #ffffff; + --vscode-tab-inactiveForeground: #949494; + --vscode-tab-unfocusedActiveForeground: rgba(255, 255, 255, 0.5); + --vscode-tab-unfocusedInactiveForeground: rgba(148, 148, 148, 0.5); + --vscode-tab-hoverBackground: #20252b; + --vscode-tab-unfocusedHoverBackground: #1e1e1e; + --vscode-tab-border: #181818; + --vscode-tab-lastPinnedBorder: rgba(255, 255, 255, 0.07); + --vscode-tab-activeBorderTop: #3a83d0; + --vscode-tab-unfocusedActiveBorderTop: rgba(58, 131, 208, 0.5); + --vscode-tab-selectedBorderTop: #3a83d0; + --vscode-tab-selectedBackground: #20252b; + --vscode-tab-selectedForeground: #ffffff; + --vscode-tab-dragAndDropBorder: #ffffff; + --vscode-tab-activeModifiedBorder: rgba(255, 60, 0, 0.53); + --vscode-tab-inactiveModifiedBorder: rgba(255, 60, 0, 0.27); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(255, 60, 0, 0.27); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(255, 60, 0, 0.13); + --vscode-editorPane-background: rgba(0, 0, 0, 0.27); + --vscode-editorGroupHeader-tabsBackground: #1e1e1e; + --vscode-editorGroupHeader-noTabsBackground: #1e1e1e; + --vscode-editorGroup-border: #181818; + --vscode-editorGroup-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-editorGroup-dropIntoPromptForeground: #cccccc; + --vscode-editorGroup-dropIntoPromptBackground: #252526; + --vscode-sideBySideEditor-horizontalBorder: #181818; + --vscode-sideBySideEditor-verticalBorder: #181818; + --vscode-banner-background: #04395e; + --vscode-banner-foreground: #ffffff; + --vscode-banner-iconForeground: #3794ff; + --vscode-statusBar-foreground: #646464; + --vscode-statusBar-noFolderForeground: #646464; + --vscode-statusBar-background: #181818; + --vscode-statusBar-noFolderBackground: #252526; + --vscode-statusBar-border: #181818; + --vscode-statusBar-focusBorder: #646464; + --vscode-statusBar-noFolderBorder: #181818; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #646464; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #646464; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #646464; + --vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5); + --vscode-statusBarItem-prominentHoverForeground: #646464; + --vscode-statusBarItem-prominentHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-errorBackground: #c72e0f; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #646464; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #7a6400; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #646464; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #181818; + --vscode-activityBar-foreground: #949494; + --vscode-activityBar-inactiveForeground: rgba(148, 148, 148, 0.4); + --vscode-activityBar-border: #181818; + --vscode-activityBar-activeBorder: #3a83d0; + --vscode-activityBar-activeBackground: rgba(58, 131, 208, 0.07); + --vscode-activityBar-dropBorder: #949494; + --vscode-activityBarBadge-background: #007acc; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-activityBarTop-foreground: #e7e7e7; + --vscode-activityBarTop-activeBorder: #e7e7e7; + --vscode-activityBarTop-inactiveForeground: rgba(231, 231, 231, 0.6); + --vscode-activityBarTop-dropBorder: #e7e7e7; + --vscode-panel-background: #1b1b1b; + --vscode-panel-border: #181818; + --vscode-panelTitle-activeForeground: #e7e7e7; + --vscode-panelTitle-inactiveForeground: rgba(231, 231, 231, 0.6); + --vscode-panelTitle-activeBorder: #e7e7e7; + --vscode-panelTitleBadge-background: #007acc; + --vscode-panelTitleBadge-foreground: #ffffff; + --vscode-panel-dropBorder: #e7e7e7; + --vscode-panelSection-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: #181818; + --vscode-panelStickyScroll-background: #1b1b1b; + --vscode-panelStickyScroll-shadow: rgba(0, 0, 0, 0.13); + --vscode-profileBadge-background: #4d4d4d; + --vscode-profileBadge-foreground: #ffffff; + --vscode-statusBarItem-remoteBackground: #181818; + --vscode-statusBarItem-remoteForeground: #646464; + --vscode-statusBarItem-remoteHoverForeground: #646464; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #646464; + --vscode-statusBarItem-offlineHoverForeground: #646464; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #007acc; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #1b1b1b; + --vscode-sideBar-foreground: rgba(255, 255, 255, 0.53); + --vscode-sideBar-border: #181818; + --vscode-sideBarTitle-background: #1b1b1b; + --vscode-sideBarTitle-foreground: #bbbbbb; + --vscode-sideBar-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-sideBarSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-sideBarSectionHeader-foreground: rgba(255, 255, 255, 0.53); + --vscode-sideBarStickyScroll-background: #1b1b1b; + --vscode-sideBarStickyScroll-shadow: rgba(0, 0, 0, 0.13); + --vscode-titleBar-activeForeground: #cccccc; + --vscode-titleBar-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-titleBar-activeBackground: #181818; + --vscode-titleBar-inactiveBackground: rgba(24, 24, 24, 0.6); + --vscode-titleBar-border: #181818; + --vscode-menubar-selectionForeground: #cccccc; + --vscode-menubar-selectionBackground: rgba(90, 93, 94, 0.31); + --vscode-commandCenter-foreground: #cccccc; + --vscode-commandCenter-activeForeground: #cccccc; + --vscode-commandCenter-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-commandCenter-background: rgba(255, 255, 255, 0.05); + --vscode-commandCenter-activeBackground: rgba(255, 255, 255, 0.08); + --vscode-commandCenter-border: rgba(204, 204, 204, 0.2); + --vscode-commandCenter-activeBorder: rgba(204, 204, 204, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(204, 204, 204, 0.15); + --vscode-notifications-foreground: #cccccc; + --vscode-notifications-background: #252526; + --vscode-notificationLink-foreground: #3794ff; + --vscode-notificationCenterHeader-background: #303031; + --vscode-notifications-border: #303031; + --vscode-notificationsErrorIcon-foreground: #f14c4c; + --vscode-notificationsWarningIcon-foreground: #cca700; + --vscode-notificationsInfoIcon-foreground: #3794ff; + --vscode-debugToolBar-background: #333333; + --vscode-debugIcon-startForeground: #89d185; + --vscode-inlineChat-foreground: #cccccc; + --vscode-inlineChat-background: #252526; + --vscode-inlineChat-border: #454545; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.13); + --vscode-inlineChatInput-border: #454545; + --vscode-inlineChatInput-focusBorder: #007fd4; + --vscode-inlineChatInput-placeholderForeground: rgba(204, 204, 204, 0.5); + --vscode-inlineChatInput-background: #3c3c3c; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.1); + --vscode-editorOverviewRuler-inlineChatInserted: rgba(156, 204, 44, 0.12); + --vscode-editorMinimap-inlineChatInserted: rgba(156, 204, 44, 0.12); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-editorOverviewRuler-inlineChatRemoved: rgba(255, 0, 0, 0.12); + --vscode-editorWatermark-foreground: rgba(212, 212, 212, 0.6); + --vscode-extensionButton-background: #0e639c; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #1177bb; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #0e639c; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: #1177bb; + --vscode-extensionIcon-verifiedForeground: #3794ff; + --vscode-chat-requestBorder: rgba(255, 255, 255, 0.1); + --vscode-chat-requestBackground: rgba(30, 30, 30, 0.62); + --vscode-chat-slashCommandBackground: rgba(52, 65, 75, 0.56); + --vscode-chat-slashCommandForeground: #40a6ff; + --vscode-chat-avatarBackground: #1f1f1f; + --vscode-chat-avatarForeground: #cccccc; + --vscode-chat-editedFileForeground: #e2c08d; + --vscode-commentsView-resolvedIcon: rgba(204, 204, 204, 0.5); + --vscode-commentsView-unresolvedIcon: #007fd4; + --vscode-editorCommentsWidget-replyInputBackground: #007acc; + --vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #007fd4; + --vscode-editorCommentsWidget-rangeBackground: rgba(0, 127, 212, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(0, 127, 212, 0.1); + --vscode-notebook-cellBorderColor: #37373d; + --vscode-notebook-focusedEditorBorder: #007fd4; + --vscode-notebookStatusSuccessIcon-foreground: #89d185; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #89d185; + --vscode-notebookStatusErrorIcon-foreground: #f48771; + --vscode-notebookStatusRunningIcon-foreground: #cccccc; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #37373d; + --vscode-notebook-selectedCellBorder: #37373d; + --vscode-notebook-focusedCellBorder: #007fd4; + --vscode-notebook-inactiveFocusedCellBorder: #37373d; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, 0.15); + --vscode-notebook-cellInsertionIndicator: #007fd4; + --vscode-notebookScrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-notebook-cellEditorBackground: #1b1b1b; + --vscode-notebook-editorBackground: rgba(0, 0, 0, 0.27); + --vscode-editorGutter-modifiedBackground: #1b81a8; + --vscode-editorGutter-addedBackground: #487e02; + --vscode-editorGutter-deletedBackground: #f14c4c; + --vscode-minimapGutter-modifiedBackground: #1b81a8; + --vscode-minimapGutter-addedBackground: #487e02; + --vscode-minimapGutter-deletedBackground: #f14c4c; + --vscode-editorOverviewRuler-modifiedForeground: rgba(27, 129, 168, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 126, 2, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(241, 76, 76, 0.6); + --vscode-editorGutter-itemGlyphForeground: #d4d4d4; + --vscode-editorGutter-itemBackground: #37373d; + --vscode-terminal-foreground: #cccccc; + --vscode-terminal-selectionBackground: #264f78; + --vscode-terminal-inactiveSelectionBackground: rgba(38, 79, 120, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, 0.25); + --vscode-terminalCommandDecoration-successBackground: #1b81a8; + --vscode-terminalCommandDecoration-errorBackground: #f14c4c; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: #181818; + --vscode-terminalOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-terminal-findMatchBackground: #515c6a; + --vscode-terminal-hoverHighlightBackground: rgba(38, 79, 120, 0.13); + --vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-terminal-initialHintForeground: rgba(255, 255, 255, 0.34); + --vscode-scmGraph-historyItemRefColor: #3794ff; + --vscode-scmGraph-historyItemRemoteRefColor: #b180d7; + --vscode-scmGraph-historyItemBaseRefColor: #ea5c00; + --vscode-scmGraph-historyItemHoverDefaultLabelForeground: #cccccc; + --vscode-scmGraph-historyItemHoverDefaultLabelBackground: #4d4d4d; + --vscode-scmGraph-historyItemHoverLabelForeground: #ffffff; + --vscode-scmGraph-historyItemHoverAdditionsForeground: #81b88b; + --vscode-scmGraph-historyItemHoverDeletionsForeground: #c74e39; + --vscode-scmGraph-foreground1: #ffb000; + --vscode-scmGraph-foreground2: #dc267f; + --vscode-scmGraph-foreground3: #994f00; + --vscode-scmGraph-foreground4: #40b0a6; + --vscode-scmGraph-foreground5: #b66dff; + --vscode-editorGutter-commentRangeForeground: #37373d; + --vscode-editorOverviewRuler-commentForeground: #37373d; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #37373d; + --vscode-editorGutter-commentGlyphForeground: #d4d4d4; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #d4d4d4; + --vscode-ports-iconRunningProcessForeground: #181818; + --vscode-settings-headerForeground: #e7e7e7; + --vscode-settings-settingsHeaderHoverForeground: rgba(231, 231, 231, 0.7); + --vscode-settings-modifiedItemIndicator: #0c7d9d; + --vscode-settings-headerBorder: #181818; + --vscode-settings-sashBorder: #181818; + --vscode-settings-dropdownBackground: #3c3c3c; + --vscode-settings-dropdownForeground: #f0f0f0; + --vscode-settings-dropdownBorder: #3c3c3c; + --vscode-settings-dropdownListBorder: #454545; + --vscode-settings-checkboxBackground: #3c3c3c; + --vscode-settings-checkboxForeground: #f0f0f0; + --vscode-settings-checkboxBorder: #3c3c3c; + --vscode-settings-textInputBackground: #3c3c3c; + --vscode-settings-textInputForeground: #cccccc; + --vscode-settings-numberInputBackground: #3c3c3c; + --vscode-settings-numberInputForeground: #cccccc; + --vscode-settings-focusedRowBackground: rgba(42, 45, 46, 0.6); + --vscode-settings-rowHoverBackground: rgba(42, 45, 46, 0.3); + --vscode-settings-focusedRowBorder: #007fd4; + --vscode-keybindingTable-headerBackground: rgba(204, 204, 204, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(204, 204, 204, 0.04); + --vscode-debugExceptionWidget-border: #a31515; + --vscode-debugExceptionWidget-background: #420b0d; + --vscode-editor-inlineValuesForeground: rgba(255, 255, 255, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3); + --vscode-multiDiffEditor-headerBackground: #262626; + --vscode-multiDiffEditor-background: #1e1e1e; + --vscode-minimap-chatEditHighlight: rgba(30, 30, 30, 0.6); + --vscode-gauge-background: #007acc; + --vscode-gauge-foreground: rgba(0, 122, 204, 0.3); + --vscode-gauge-warningBackground: #b89500; + --vscode-gauge-warningForeground: rgba(184, 149, 0, 0.3); + --vscode-gauge-errorBackground: #be1100; + --vscode-gauge-errorForeground: rgba(190, 17, 0, 0.3); + --vscode-interactive-activeCodeBorder: rgba(0, 122, 204, 0); + --vscode-interactive-inactiveCodeBorder: #37373d; + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #f14c4c; + --vscode-testing-messagePeekBorder: #3794ff; + --vscode-testing-peekHeaderBackground: rgba(241, 76, 76, 0.1); + --vscode-testing-messagePeekHeaderBackground: rgba(55, 148, 255, 0.1); + --vscode-testing-coveredBackground: rgba(156, 204, 44, 0.2); + --vscode-testing-coveredBorder: rgba(156, 204, 44, 0.15); + --vscode-testing-coveredGutterBackground: rgba(156, 204, 44, 0.12); + --vscode-testing-uncoveredBranchBackground: #781212; + --vscode-testing-uncoveredBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-uncoveredBorder: rgba(255, 0, 0, 0.15); + --vscode-testing-uncoveredGutterBackground: rgba(255, 0, 0, 0.3); + --vscode-testing-coverCountBadgeBackground: #4d4d4d; + --vscode-testing-coverCountBadgeForeground: #ffffff; + --vscode-testing-message\.error\.badgeBackground: #f14c4c; + --vscode-testing-message\.error\.badgeBorder: #f14c4c; + --vscode-testing-message\.error\.badgeForeground: #000000; + --vscode-testing-message\.info\.decorationForeground: rgba(212, 212, 212, 0.5); + --vscode-testing-iconErrored\.retired: rgba(241, 76, 76, 0.7); + --vscode-testing-iconFailed\.retired: rgba(241, 76, 76, 0.7); + --vscode-testing-iconPassed\.retired: rgba(115, 201, 145, 0.7); + --vscode-testing-iconQueued\.retired: rgba(204, 167, 0, 0.7); + --vscode-testing-iconUnset\.retired: rgba(132, 132, 132, 0.7); + --vscode-testing-iconSkipped\.retired: rgba(132, 132, 132, 0.7); + --vscode-statusBar-debuggingBackground: #cc6633; + --vscode-statusBar-debuggingForeground: #646464; + --vscode-statusBar-debuggingBorder: #181818; + --vscode-commandCenter-debuggingBackground: rgba(204, 102, 51, 0.26); + --vscode-debugTokenExpression-name: #c586c0; + --vscode-debugTokenExpression-type: #4a90e2; + --vscode-debugTokenExpression-value: rgba(204, 204, 204, 0.6); + --vscode-debugTokenExpression-string: #ce9178; + --vscode-debugTokenExpression-boolean: #4e94ce; + --vscode-debugTokenExpression-number: #b5cea8; + --vscode-debugTokenExpression-error: #f48771; + --vscode-debugView-exceptionLabelForeground: #cccccc; + --vscode-debugView-exceptionLabelBackground: #6c2022; + --vscode-debugView-stateLabelForeground: #cccccc; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #3794ff; + --vscode-debugConsole-warningForeground: #cca700; + --vscode-debugConsole-errorForeground: #f48771; + --vscode-debugConsole-sourceForeground: #cccccc; + --vscode-debugConsoleInputIcon-foreground: #cccccc; + --vscode-debugIcon-pauseForeground: #75beff; + --vscode-debugIcon-stopForeground: #f48771; + --vscode-debugIcon-disconnectForeground: #f48771; + --vscode-debugIcon-restartForeground: #89d185; + --vscode-debugIcon-stepOverForeground: #75beff; + --vscode-debugIcon-stepIntoForeground: #75beff; + --vscode-debugIcon-stepOutForeground: #75beff; + --vscode-debugIcon-continueForeground: #75beff; + --vscode-debugIcon-stepBackForeground: #75beff; + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.2); + --vscode-mergeEditor-changeBase\.background: #4b1818; + --vscode-mergeEditor-changeBase\.word\.background: #6f1313; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, 0.48); + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-extensionIcon-starForeground: #ff8e00; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #d758b3; + --vscode-extensionIcon-privateForeground: rgba(255, 255, 255, 0.38); + --vscode-terminal-ansiBlack: #000000; + --vscode-terminal-ansiRed: #cd3131; + --vscode-terminal-ansiGreen: #0dbc79; + --vscode-terminal-ansiYellow: #e5e510; + --vscode-terminal-ansiBlue: #2472c8; + --vscode-terminal-ansiMagenta: #bc3fbc; + --vscode-terminal-ansiCyan: #11a8cd; + --vscode-terminal-ansiWhite: #e5e5e5; + --vscode-terminal-ansiBrightBlack: #666666; + --vscode-terminal-ansiBrightRed: #f14c4c; + --vscode-terminal-ansiBrightGreen: #23d18b; + --vscode-terminal-ansiBrightYellow: #f5f543; + --vscode-terminal-ansiBrightBlue: #3b8eea; + --vscode-terminal-ansiBrightMagenta: #d670d6; + --vscode-terminal-ansiBrightCyan: #29b8db; + --vscode-terminal-ansiBrightWhite: #e5e5e5; + --vscode-simpleFindWidget-sashBorder: #454545; + --vscode-terminalStickyScrollHover-background: #2a2d2e; + --vscode-terminalCommandGuide-foreground: #37373d; + --vscode-terminalSymbolIcon-flagForeground: #ee9d28; + --vscode-terminalSymbolIcon-aliasForeground: #b180d7; + --vscode-terminalSymbolIcon-optionValueForeground: #75beff; + --vscode-terminalSymbolIcon-methodForeground: #b180d7; + --vscode-terminalSymbolIcon-argumentForeground: #75beff; + --vscode-terminalSymbolIcon-optionForeground: #ee9d28; + --vscode-terminalSymbolIcon-fileForeground: #cccccc; + --vscode-terminalSymbolIcon-folderForeground: #cccccc; + --vscode-welcomePage-tileBackground: #252526; + --vscode-welcomePage-tileHoverBackground: #2c2c2d; + --vscode-welcomePage-tileBorder: rgba(255, 255, 255, 0.1); + --vscode-welcomePage-progress\.background: #3c3c3c; + --vscode-welcomePage-progress\.foreground: #3794ff; + --vscode-walkthrough-stepTitle\.foreground: #ffffff; + --vscode-walkThrough-embeddedEditorBackground: rgba(0, 0, 0, 0.4); + --vscode-profiles-sashBorder: #181818; + --vscode-gitDecoration-addedResourceForeground: #81b88b; + --vscode-gitDecoration-modifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-deletedResourceForeground: #c74e39; + --vscode-gitDecoration-renamedResourceForeground: #73c991; + --vscode-gitDecoration-untrackedResourceForeground: #73c991; + --vscode-gitDecoration-ignoredResourceForeground: #4b4b4b; + --vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-stageDeletedResourceForeground: #c74e39; + --vscode-gitDecoration-conflictingResourceForeground: #e4676b; + --vscode-gitDecoration-submoduleResourceForeground: #8db9e2; + --vscode-git-blame\.editorDecorationForeground: #969696; + --vscode-gitlens-gutterBackgroundColor: rgba(255, 255, 255, 0.07); + --vscode-gitlens-gutterForegroundColor: #bebebe; + --vscode-gitlens-gutterUncommittedForegroundColor: rgba(0, 188, 242, 0.6); + --vscode-gitlens-trailingLineBackgroundColor: rgba(0, 0, 0, 0); + --vscode-gitlens-trailingLineForegroundColor: rgba(153, 153, 153, 0.35); + --vscode-gitlens-lineHighlightBackgroundColor: rgba(0, 188, 242, 0.2); + --vscode-gitlens-lineHighlightOverviewRulerColor: rgba(0, 188, 242, 0.6); + --vscode-gitlens-openAutolinkedIssueIconColor: #3fb950; + --vscode-gitlens-closedAutolinkedIssueIconColor: #a371f7; + --vscode-gitlens-closedPullRequestIconColor: #f85149; + --vscode-gitlens-openPullRequestIconColor: #3fb950; + --vscode-gitlens-mergedPullRequestIconColor: #a371f7; + --vscode-gitlens-unpublishedChangesIconColor: #35b15e; + --vscode-gitlens-unpublishedCommitIconColor: #35b15e; + --vscode-gitlens-unpulledChangesIconColor: #b15e35; + --vscode-gitlens-decorations\.addedForegroundColor: #81b88b; + --vscode-gitlens-decorations\.copiedForegroundColor: #73c991; + --vscode-gitlens-decorations\.deletedForegroundColor: #c74e39; + --vscode-gitlens-decorations\.ignoredForegroundColor: #4b4b4b; + --vscode-gitlens-decorations\.modifiedForegroundColor: #e2c08d; + --vscode-gitlens-decorations\.untrackedForegroundColor: #73c991; + --vscode-gitlens-decorations\.renamedForegroundColor: #73c991; + --vscode-gitlens-decorations\.branchAheadForegroundColor: #35b15e; + --vscode-gitlens-decorations\.branchBehindForegroundColor: #b15e35; + --vscode-gitlens-decorations\.branchDivergedForegroundColor: #d8af1b; + --vscode-gitlens-decorations\.branchUpToDateForegroundColor: rgba(255, 255, 255, 0.53); + --vscode-gitlens-decorations\.branchUnpublishedForegroundColor: rgba(255, 255, 255, 0.53); + --vscode-gitlens-decorations\.branchMissingUpstreamForegroundColor: #c74e39; + --vscode-gitlens-decorations\.statusMergingOrRebasingConflictForegroundColor: #c74e39; + --vscode-gitlens-decorations\.statusMergingOrRebasingForegroundColor: #d8af1b; + --vscode-gitlens-decorations\.workspaceRepoMissingForegroundColor: #909090; + --vscode-gitlens-decorations\.workspaceCurrentForegroundColor: #35b15e; + --vscode-gitlens-decorations\.workspaceRepoOpenForegroundColor: #35b15e; + --vscode-gitlens-decorations\.worktreeHasUncommittedChangesForegroundColor: #e2c08d; + --vscode-gitlens-decorations\.worktreeMissingForegroundColor: #c74e39; + --vscode-gitlens-graphLane1Color: #15a0bf; + --vscode-gitlens-graphLane2Color: #0669f7; + --vscode-gitlens-graphLane3Color: #8e00c2; + --vscode-gitlens-graphLane4Color: #c517b6; + --vscode-gitlens-graphLane5Color: #d90171; + --vscode-gitlens-graphLane6Color: #cd0101; + --vscode-gitlens-graphLane7Color: #f25d2e; + --vscode-gitlens-graphLane8Color: #f2ca33; + --vscode-gitlens-graphLane9Color: #7bd938; + --vscode-gitlens-graphLane10Color: #2ece9d; + --vscode-gitlens-graphChangesColumnAddedColor: #347d39; + --vscode-gitlens-graphChangesColumnDeletedColor: #c93c37; + --vscode-gitlens-graphMinimapMarkerHeadColor: #05e617; + --vscode-gitlens-graphScrollMarkerHeadColor: #05e617; + --vscode-gitlens-graphMinimapMarkerUpstreamColor: #09ae17; + --vscode-gitlens-graphScrollMarkerUpstreamColor: #09ae17; + --vscode-gitlens-graphMinimapMarkerHighlightsColor: #fbff0a; + --vscode-gitlens-graphScrollMarkerHighlightsColor: #fbff0a; + --vscode-gitlens-graphMinimapMarkerLocalBranchesColor: #3087cf; + --vscode-gitlens-graphScrollMarkerLocalBranchesColor: #3087cf; + --vscode-gitlens-graphMinimapMarkerPullRequestsColor: #c76801; + --vscode-gitlens-graphScrollMarkerPullRequestsColor: #c76801; + --vscode-gitlens-graphMinimapMarkerRemoteBranchesColor: #2b5e88; + --vscode-gitlens-graphScrollMarkerRemoteBranchesColor: #2b5e88; + --vscode-gitlens-graphMinimapMarkerStashesColor: #b34db3; + --vscode-gitlens-graphScrollMarkerStashesColor: #b34db3; + --vscode-gitlens-graphMinimapMarkerTagsColor: #6b562e; + --vscode-gitlens-graphScrollMarkerTagsColor: #6b562e; + --vscode-gitlens-launchpadIndicatorMergeableColor: #3fb950; + --vscode-gitlens-launchpadIndicatorMergeableHoverColor: #3fb950; + --vscode-gitlens-launchpadIndicatorBlockedColor: #c74e39; + --vscode-gitlens-launchpadIndicatorBlockedHoverColor: #c74e39; + --vscode-gitlens-launchpadIndicatorAttentionColor: #d8af1b; + --vscode-gitlens-launchpadIndicatorAttentionHoverColor: #d8af1b; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/dark-abyss.scss b/vendor/mynah-ui/example/src/styles/themes/dark-abyss.scss new file mode 100644 index 00000000..b1cfde2f --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/dark-abyss.scss @@ -0,0 +1,603 @@ +html[theme='dark-abyss']:root { + --vscode-foreground: #cccccc; + --vscode-disabledForeground: rgba(204, 204, 204, 0.5); + --vscode-errorForeground: #f48771; + --vscode-descriptionForeground: rgba(204, 204, 204, 0.7); + --vscode-icon-foreground: #c5c5c5; + --vscode-focusBorder: #596f99; + --vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18); + --vscode-textLink-foreground: #3794ff; + --vscode-textLink-activeForeground: #3794ff; + --vscode-textPreformat-foreground: #d7ba7d; + --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1); + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4); + --vscode-widget-shadow: rgba(0, 0, 0, 0.36); + --vscode-input-background: #181f2f; + --vscode-input-foreground: #cccccc; + --vscode-inputOption-activeBorder: #1d4a87; + --vscode-inputOption-hoverBackground: rgba(90, 93, 94, 0.5); + --vscode-inputOption-activeBackground: rgba(89, 111, 153, 0.4); + --vscode-inputOption-activeForeground: #ffffff; + --vscode-input-placeholderForeground: rgba(204, 204, 204, 0.5); + --vscode-inputValidation-infoBackground: #051336; + --vscode-inputValidation-infoBorder: #384078; + --vscode-inputValidation-warningBackground: #5b7e7a; + --vscode-inputValidation-warningBorder: #5b7e7a; + --vscode-inputValidation-errorBackground: #a22d44; + --vscode-inputValidation-errorBorder: #ab395b; + --vscode-dropdown-background: #181f2f; + --vscode-dropdown-foreground: #f0f0f0; + --vscode-dropdown-border: #181f2f; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #2b3c5d; + --vscode-button-hoverBackground: #344870; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #3a3d41; + --vscode-button-secondaryHoverBackground: #45494e; + --vscode-badge-background: #0063a5; + --vscode-badge-foreground: #ffffff; + --vscode-scrollbar-shadow: rgba(81, 94, 145, 0.67); + --vscode-scrollbarSlider-background: rgba(31, 34, 48, 0.67); + --vscode-scrollbarSlider-hoverBackground: rgba(59, 63, 81, 0.53); + --vscode-scrollbarSlider-activeBackground: rgba(59, 63, 81, 0.53); + --vscode-progressBar-background: #0063a5; + --vscode-editorError-foreground: #f14c4c; + --vscode-editorWarning-foreground: #cca700; + --vscode-editorInfo-foreground: #3794ff; + --vscode-editorHint-foreground: rgba(238, 238, 238, 0.7); + --vscode-sash-hoverBorder: #596f99; + --vscode-editor-background: #000c18; + --vscode-editor-foreground: #6688cc; + --vscode-editorStickyScroll-background: #000c18; + --vscode-editorStickyScrollHover-background: #2a2d2e; + --vscode-editorWidget-background: #262641; + --vscode-editorWidget-foreground: #cccccc; + --vscode-editorWidget-border: #454545; + --vscode-quickInput-background: #262641; + --vscode-quickInput-foreground: #cccccc; + --vscode-quickInputTitle-background: rgba(255, 255, 255, 0.1); + --vscode-pickerGroup-foreground: #596f99; + --vscode-pickerGroup-border: #596f99; + --vscode-keybindingLabel-background: rgba(128, 128, 128, 0.17); + --vscode-keybindingLabel-foreground: #cccccc; + --vscode-keybindingLabel-border: rgba(51, 51, 51, 0.6); + --vscode-keybindingLabel-bottomBorder: rgba(68, 68, 68, 0.6); + --vscode-editor-selectionBackground: #770811; + --vscode-editor-inactiveSelectionBackground: rgba(119, 8, 17, 0.5); + --vscode-editor-selectionHighlightBackground: rgba(86, 6, 12, 0.6); + --vscode-editor-findMatchBackground: #515c6a; + --vscode-editor-findMatchHighlightBackground: rgba(238, 238, 238, 0.27); + --vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4); + --vscode-searchEditor-findMatchBackground: rgba(238, 238, 238, 0.18); + --vscode-search-resultsInfoForeground: rgba(204, 204, 204, 0.65); + --vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25); + --vscode-editorHoverWidget-background: #000c38; + --vscode-editorHoverWidget-foreground: #cccccc; + --vscode-editorHoverWidget-border: #004c18; + --vscode-editorHoverWidget-statusBarBackground: #000f43; + --vscode-editorLink-activeForeground: #0063a5; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(0, 99, 165, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(0, 99, 165, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(0, 99, 165, 0.1); + --vscode-editorLightBulb-foreground: #ffcc00; + --vscode-editorLightBulbAutoFix-foreground: #75beff; + --vscode-diffEditor-insertedTextBackground: rgba(49, 149, 138, 0.33); + --vscode-diffEditor-removedTextBackground: rgba(137, 47, 70, 0.53); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(204, 204, 204, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #3e3e3e; + --vscode-diffEditor-unchangedRegionForeground: #a3a2a2; + --vscode-diffEditor-unchangedCodeBackground: rgba(116, 116, 116, 0.16); + --vscode-list-focusOutline: #596f99; + --vscode-list-activeSelectionBackground: #08286b; + --vscode-list-activeSelectionForeground: #ffffff; + --vscode-list-inactiveSelectionBackground: #152037; + --vscode-list-hoverBackground: #061940; + --vscode-list-dropBackground: #041d52; + --vscode-list-highlightForeground: #0063a5; + --vscode-list-focusHighlightForeground: #0063a5; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #f88070; + --vscode-list-warningForeground: #cca700; + --vscode-listFilterWidget-background: #262641; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.36); + --vscode-list-filterMatchBackground: rgba(238, 238, 238, 0.27); + --vscode-tree-indentGuidesStroke: #585858; + --vscode-tree-inactiveIndentGuidesStroke: rgba(88, 88, 88, 0.4); + --vscode-tree-tableColumnsBorder: rgba(204, 204, 204, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(204, 204, 204, 0.04); + --vscode-list-deemphasizedForeground: #8c8c8c; + --vscode-checkbox-background: #181f2f; + --vscode-checkbox-selectBackground: #262641; + --vscode-checkbox-foreground: #f0f0f0; + --vscode-checkbox-border: #181f2f; + --vscode-checkbox-selectBorder: #c5c5c5; + --vscode-quickInputList-focusForeground: #ffffff; + --vscode-quickInputList-focusBackground: #08286b; + --vscode-menu-foreground: #f0f0f0; + --vscode-menu-background: #181f2f; + --vscode-menu-selectionForeground: #ffffff; + --vscode-menu-selectionBackground: #08286b; + --vscode-menu-separatorBackground: #606060; + --vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31); + --vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, 0.3); + --vscode-editor-snippetFinalTabstopHighlightBorder: #525252; + --vscode-breadcrumb-foreground: rgba(204, 204, 204, 0.8); + --vscode-breadcrumb-background: #000c18; + --vscode-breadcrumb-focusForeground: #e0e0e0; + --vscode-breadcrumb-activeSelectionForeground: #e0e0e0; + --vscode-breadcrumbPicker-background: #262641; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #676767; + --vscode-minimap-selectionHighlight: #750000; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #cca700; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(31, 34, 48, 0.33); + --vscode-minimapSlider-hoverBackground: rgba(59, 63, 81, 0.27); + --vscode-minimapSlider-activeBackground: rgba(59, 63, 81, 0.27); + --vscode-problemsErrorIcon-foreground: #f14c4c; + --vscode-problemsWarningIcon-foreground: #cca700; + --vscode-problemsInfoIcon-foreground: #3794ff; + --vscode-charts-foreground: #cccccc; + --vscode-charts-lines: rgba(204, 204, 204, 0.5); + --vscode-charts-red: #f14c4c; + --vscode-charts-blue: #3794ff; + --vscode-charts-yellow: #cca700; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #89d185; + --vscode-charts-purple: #b180d7; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #cccccc; + --vscode-symbolIcon-booleanForeground: #cccccc; + --vscode-symbolIcon-classForeground: #ee9d28; + --vscode-symbolIcon-colorForeground: #cccccc; + --vscode-symbolIcon-constantForeground: #cccccc; + --vscode-symbolIcon-constructorForeground: #b180d7; + --vscode-symbolIcon-enumeratorForeground: #ee9d28; + --vscode-symbolIcon-enumeratorMemberForeground: #75beff; + --vscode-symbolIcon-eventForeground: #ee9d28; + --vscode-symbolIcon-fieldForeground: #75beff; + --vscode-symbolIcon-fileForeground: #cccccc; + --vscode-symbolIcon-folderForeground: #cccccc; + --vscode-symbolIcon-functionForeground: #b180d7; + --vscode-symbolIcon-interfaceForeground: #75beff; + --vscode-symbolIcon-keyForeground: #cccccc; + --vscode-symbolIcon-keywordForeground: #cccccc; + --vscode-symbolIcon-methodForeground: #b180d7; + --vscode-symbolIcon-moduleForeground: #cccccc; + --vscode-symbolIcon-namespaceForeground: #cccccc; + --vscode-symbolIcon-nullForeground: #cccccc; + --vscode-symbolIcon-numberForeground: #cccccc; + --vscode-symbolIcon-objectForeground: #cccccc; + --vscode-symbolIcon-operatorForeground: #cccccc; + --vscode-symbolIcon-packageForeground: #cccccc; + --vscode-symbolIcon-propertyForeground: #cccccc; + --vscode-symbolIcon-referenceForeground: #cccccc; + --vscode-symbolIcon-snippetForeground: #cccccc; + --vscode-symbolIcon-stringForeground: #cccccc; + --vscode-symbolIcon-structForeground: #cccccc; + --vscode-symbolIcon-textForeground: #cccccc; + --vscode-symbolIcon-typeParameterForeground: #cccccc; + --vscode-symbolIcon-unitForeground: #cccccc; + --vscode-symbolIcon-variableForeground: #75beff; + --vscode-actionBar-toggledBackground: rgba(89, 111, 153, 0.4); + --vscode-editorHoverWidget-highlightForeground: #0063a5; + --vscode-editor-lineHighlightBackground: #082050; + --vscode-editor-lineHighlightBorder: #282828; + --vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-editor-symbolHighlightBackground: rgba(238, 238, 238, 0.27); + --vscode-editorCursor-foreground: #ddbb88; + --vscode-editorWhitespace-foreground: #103050; + --vscode-editorLineNumber-foreground: #406385; + --vscode-editorIndentGuide-background: #002952; + --vscode-editorIndentGuide-activeBackground: #204972; + --vscode-editorIndentGuide-background1: #002952; + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: #204972; + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #c6c6c6; + --vscode-editorLineNumber-activeForeground: #80a2c2; + --vscode-editorRuler-foreground: #5a5a5a; + --vscode-editorCodeLens-foreground: #999999; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #888888; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #000c18; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67); + --vscode-editorGhostText-foreground: rgba(255, 255, 255, 0.34); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #cca700; + --vscode-editorOverviewRuler-infoForeground: #3794ff; + --vscode-editorBracketHighlight-foreground1: #ffd700; + --vscode-editorBracketHighlight-foreground2: #da70d6; + --vscode-editorBracketHighlight-foreground3: #179fff; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #bd9b03; + --vscode-editorUnicodeHighlight-background: rgba(189, 155, 3, 0.15); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(119, 8, 17, 0.3); + --vscode-editorGutter-foldingControlForeground: #c5c5c5; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.72); + --vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, 0.72); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.72); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: #10192c; + --vscode-peekViewTitleLabel-foreground: #ffffff; + --vscode-peekViewTitleDescription-foreground: rgba(204, 204, 204, 0.7); + --vscode-peekView-border: #2b2b4a; + --vscode-peekViewResult-background: #060621; + --vscode-peekViewResult-lineForeground: #bbbbbb; + --vscode-peekViewResult-fileForeground: #ffffff; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #ffffff; + --vscode-peekViewEditor-background: #10192c; + --vscode-peekViewEditorGutter-background: #10192c; + --vscode-peekViewEditorStickyScroll-background: #10192c; + --vscode-peekViewResult-matchHighlightBackground: rgba(238, 238, 238, 0.27); + --vscode-peekViewEditor-matchHighlightBackground: rgba(238, 238, 238, 0.2); + --vscode-editorMarkerNavigationError-background: #ab395b; + --vscode-editorMarkerNavigationError-headerBackground: rgba(171, 57, 91, 0.1); + --vscode-editorMarkerNavigationWarning-background: #5b7e7a; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(91, 126, 122, 0.1); + --vscode-editorMarkerNavigationInfo-background: #3794ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, 0.1); + --vscode-editorMarkerNavigation-background: #060621; + --vscode-editorSuggestWidget-background: #262641; + --vscode-editorSuggestWidget-border: #454545; + --vscode-editorSuggestWidget-foreground: #6688cc; + --vscode-editorSuggestWidget-selectedForeground: #ffffff; + --vscode-editorSuggestWidget-selectedBackground: #08286b; + --vscode-editorSuggestWidget-highlightForeground: #0063a5; + --vscode-editorSuggestWidget-focusHighlightForeground: #0063a5; + --vscode-editorSuggestWidgetStatus-foreground: rgba(102, 136, 204, 0.5); + --vscode-tab-activeBackground: #000c18; + --vscode-tab-unfocusedActiveBackground: #000c18; + --vscode-tab-inactiveBackground: #10192c; + --vscode-tab-unfocusedInactiveBackground: #10192c; + --vscode-tab-activeForeground: #ffffff; + --vscode-tab-inactiveForeground: rgba(255, 255, 255, 0.5); + --vscode-tab-unfocusedActiveForeground: rgba(255, 255, 255, 0.5); + --vscode-tab-unfocusedInactiveForeground: rgba(255, 255, 255, 0.25); + --vscode-tab-border: #2b2b4a; + --vscode-tab-lastPinnedBorder: #2b3c5d; + --vscode-tab-activeModifiedBorder: #3399cc; + --vscode-tab-inactiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 153, 204, 0.25); + --vscode-editorPane-background: #000c18; + --vscode-editorGroupHeader-tabsBackground: #1c1c2a; + --vscode-editorGroupHeader-noTabsBackground: #000c18; + --vscode-editorGroup-border: #2b2b4a; + --vscode-editorGroup-dropBackground: rgba(37, 55, 93, 0.67); + --vscode-editorGroup-dropIntoPromptForeground: #cccccc; + --vscode-editorGroup-dropIntoPromptBackground: #262641; + --vscode-sideBySideEditor-horizontalBorder: #2b2b4a; + --vscode-sideBySideEditor-verticalBorder: #2b2b4a; + --vscode-panel-background: #000c18; + --vscode-panel-border: #2b2b4a; + --vscode-panelTitle-activeForeground: #e7e7e7; + --vscode-panelTitle-inactiveForeground: rgba(231, 231, 231, 0.6); + --vscode-panelTitle-activeBorder: #e7e7e7; + --vscode-panel-dropBorder: #e7e7e7; + --vscode-panelSection-dropBackground: rgba(37, 55, 93, 0.67); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: #2b2b4a; + --vscode-banner-background: #08286b; + --vscode-banner-foreground: #ffffff; + --vscode-banner-iconForeground: #3794ff; + --vscode-statusBar-foreground: #ffffff; + --vscode-statusBar-noFolderForeground: #ffffff; + --vscode-statusBar-background: #10192c; + --vscode-statusBar-noFolderBackground: #10192c; + --vscode-statusBar-focusBorder: #ffffff; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #ffffff; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #ffffff; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #ffffff; + --vscode-statusBarItem-prominentBackground: #0063a5; + --vscode-statusBarItem-prominentHoverForeground: #ffffff; + --vscode-statusBarItem-prominentHoverBackground: rgba(0, 99, 165, 0.87); + --vscode-statusBarItem-errorBackground: #c72e0f; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #ffffff; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #7a6400; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #ffffff; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #051336; + --vscode-activityBar-foreground: #ffffff; + --vscode-activityBar-inactiveForeground: rgba(255, 255, 255, 0.4); + --vscode-activityBar-activeBorder: #ffffff; + --vscode-activityBar-dropBorder: #ffffff; + --vscode-activityBarBadge-background: #007acc; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-profileBadge-background: #4d4d4d; + --vscode-profileBadge-foreground: #ffffff; + --vscode-statusBarItem-remoteBackground: #0063a5; + --vscode-statusBarItem-remoteForeground: #ffffff; + --vscode-statusBarItem-remoteHoverForeground: #ffffff; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #ffffff; + --vscode-statusBarItem-offlineHoverForeground: #ffffff; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #007acc; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #060621; + --vscode-sideBar-dropBackground: rgba(37, 55, 93, 0.67); + --vscode-sideBarSectionHeader-background: #10192c; + --vscode-titleBar-activeForeground: #cccccc; + --vscode-titleBar-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-titleBar-activeBackground: #10192c; + --vscode-titleBar-inactiveBackground: rgba(16, 25, 44, 0.6); + --vscode-menubar-selectionForeground: #cccccc; + --vscode-menubar-selectionBackground: rgba(90, 93, 94, 0.31); + --vscode-notifications-foreground: #cccccc; + --vscode-notifications-background: #262641; + --vscode-notificationLink-foreground: #3794ff; + --vscode-notificationCenterHeader-background: #313155; + --vscode-notifications-border: #313155; + --vscode-notificationsErrorIcon-foreground: #f14c4c; + --vscode-notificationsWarningIcon-foreground: #cca700; + --vscode-notificationsInfoIcon-foreground: #3794ff; + --vscode-commandCenter-foreground: #cccccc; + --vscode-commandCenter-activeForeground: #cccccc; + --vscode-commandCenter-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-commandCenter-background: rgba(255, 255, 255, 0.05); + --vscode-commandCenter-activeBackground: rgba(255, 255, 255, 0.08); + --vscode-commandCenter-border: rgba(204, 204, 204, 0.2); + --vscode-commandCenter-activeBorder: rgba(204, 204, 204, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(204, 204, 204, 0.15); + --vscode-chat-requestBorder: rgba(255, 255, 255, 0.1); + --vscode-chat-slashCommandBackground: #0063a5; + --vscode-chat-slashCommandForeground: #ffffff; + --vscode-simpleFindWidget-sashBorder: #454545; + --vscode-commentsView-resolvedIcon: rgba(204, 204, 204, 0.5); + --vscode-commentsView-unresolvedIcon: #596f99; + --vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #596f99; + --vscode-editorCommentsWidget-rangeBackground: rgba(89, 111, 153, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(89, 111, 153, 0.1); + --vscode-editorGutter-commentRangeForeground: #152037; + --vscode-editorOverviewRuler-commentForeground: #152037; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #152037; + --vscode-editorGutter-commentGlyphForeground: #6688cc; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #6688cc; + --vscode-debugToolBar-background: #051336; + --vscode-debugIcon-startForeground: #89d185; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.2); + --vscode-mergeEditor-changeBase\.background: #4b1818; + --vscode-mergeEditor-changeBase\.word\.background: #6f1313; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, 0.48); + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #e7e7e7; + --vscode-settings-settingsHeaderHoverForeground: rgba(231, 231, 231, 0.7); + --vscode-settings-modifiedItemIndicator: #0c7d9d; + --vscode-settings-headerBorder: #2b2b4a; + --vscode-settings-sashBorder: #2b2b4a; + --vscode-settings-dropdownBackground: #181f2f; + --vscode-settings-dropdownForeground: #f0f0f0; + --vscode-settings-dropdownBorder: #181f2f; + --vscode-settings-dropdownListBorder: #454545; + --vscode-settings-checkboxBackground: #181f2f; + --vscode-settings-checkboxForeground: #f0f0f0; + --vscode-settings-checkboxBorder: #181f2f; + --vscode-settings-textInputBackground: #181f2f; + --vscode-settings-textInputForeground: #cccccc; + --vscode-settings-numberInputBackground: #181f2f; + --vscode-settings-numberInputForeground: #cccccc; + --vscode-settings-focusedRowBackground: rgba(6, 25, 64, 0.6); + --vscode-settings-rowHoverBackground: rgba(6, 25, 64, 0.3); + --vscode-settings-focusedRowBorder: #596f99; + --vscode-terminal-foreground: #cccccc; + --vscode-terminal-selectionBackground: #770811; + --vscode-terminal-inactiveSelectionBackground: rgba(119, 8, 17, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, 0.25); + --vscode-terminalCommandDecoration-successBackground: #1b81a8; + --vscode-terminalCommandDecoration-errorBackground: #f14c4c; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: #2b2b4a; + --vscode-terminal-findMatchBackground: #515c6a; + --vscode-terminal-hoverHighlightBackground: rgba(38, 79, 120, 0.13); + --vscode-terminal-findMatchHighlightBackground: rgba(238, 238, 238, 0.27); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(37, 55, 93, 0.67); + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #f14c4c; + --vscode-testing-peekHeaderBackground: rgba(241, 76, 76, 0.1); + --vscode-testing-message\.error\.decorationForeground: #f14c4c; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(102, 136, 204, 0.5); + --vscode-welcomePage-tileBackground: #262641; + --vscode-welcomePage-tileHoverBackground: #2e2e4e; + --vscode-welcomePage-tileBorder: rgba(255, 255, 255, 0.1); + --vscode-welcomePage-progress\.background: #181f2f; + --vscode-welcomePage-progress\.foreground: #3794ff; + --vscode-walkthrough-stepTitle\.foreground: #ffffff; + --vscode-walkThrough-embeddedEditorBackground: rgba(0, 0, 0, 0.4); + --vscode-inlineChat-background: #262641; + --vscode-inlineChat-border: #454545; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.36); + --vscode-inlineChat-regionHighlight: rgba(38, 79, 120, 0.25); + --vscode-inlineChatInput-border: #454545; + --vscode-inlineChatInput-focusBorder: #596f99; + --vscode-inlineChatInput-placeholderForeground: rgba(204, 204, 204, 0.5); + --vscode-inlineChatInput-background: #181f2f; + --vscode-inlineChatDiff-inserted: rgba(49, 149, 138, 0.17); + --vscode-inlineChatDiff-removed: rgba(137, 47, 70, 0.27); + --vscode-debugExceptionWidget-border: #ab395b; + --vscode-debugExceptionWidget-background: #051336; + --vscode-ports-iconRunningProcessForeground: #80a2c2; + --vscode-statusBar-debuggingBackground: #10192c; + --vscode-statusBar-debuggingForeground: #ffffff; + --vscode-editor-inlineValuesForeground: rgba(255, 255, 255, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: #1b81a8; + --vscode-editorGutter-addedBackground: #487e02; + --vscode-editorGutter-deletedBackground: #f14c4c; + --vscode-minimapGutter-modifiedBackground: #1b81a8; + --vscode-minimapGutter-addedBackground: #487e02; + --vscode-minimapGutter-deletedBackground: #f14c4c; + --vscode-editorOverviewRuler-modifiedForeground: rgba(27, 129, 168, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 126, 2, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(241, 76, 76, 0.6); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: #152037; + --vscode-notebook-focusedEditorBorder: #596f99; + --vscode-notebookStatusSuccessIcon-foreground: #89d185; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #89d185; + --vscode-notebookStatusErrorIcon-foreground: #f48771; + --vscode-notebookStatusRunningIcon-foreground: #cccccc; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #152037; + --vscode-notebook-selectedCellBorder: #152037; + --vscode-notebook-focusedCellBorder: #596f99; + --vscode-notebook-inactiveFocusedCellBorder: #152037; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, 0.15); + --vscode-notebook-cellInsertionIndicator: #596f99; + --vscode-notebookScrollbarSlider-background: rgba(31, 34, 48, 0.67); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(59, 63, 81, 0.53); + --vscode-notebookScrollbarSlider-activeBackground: rgba(59, 63, 81, 0.53); + --vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-notebook-cellEditorBackground: #060621; + --vscode-notebook-editorBackground: #000c18; + --vscode-keybindingTable-headerBackground: rgba(204, 204, 204, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(204, 204, 204, 0.04); + --vscode-debugTokenExpression-name: #c586c0; + --vscode-debugTokenExpression-value: rgba(204, 204, 204, 0.6); + --vscode-debugTokenExpression-string: #ce9178; + --vscode-debugTokenExpression-boolean: #4e94ce; + --vscode-debugTokenExpression-number: #b5cea8; + --vscode-debugTokenExpression-error: #f48771; + --vscode-debugView-exceptionLabelForeground: #cccccc; + --vscode-debugView-exceptionLabelBackground: #6c2022; + --vscode-debugView-stateLabelForeground: #cccccc; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #3794ff; + --vscode-debugConsole-warningForeground: #cca700; + --vscode-debugConsole-errorForeground: #f48771; + --vscode-debugConsole-sourceForeground: #cccccc; + --vscode-debugConsoleInputIcon-foreground: #cccccc; + --vscode-debugIcon-pauseForeground: #75beff; + --vscode-debugIcon-stopForeground: #f48771; + --vscode-debugIcon-disconnectForeground: #f48771; + --vscode-debugIcon-restartForeground: #89d185; + --vscode-debugIcon-stepOverForeground: #75beff; + --vscode-debugIcon-stepIntoForeground: #75beff; + --vscode-debugIcon-stepOutForeground: #75beff; + --vscode-debugIcon-continueForeground: #75beff; + --vscode-debugIcon-stepBackForeground: #75beff; + --vscode-scm-providerBorder: #454545; + --vscode-extensionButton-background: #2b3c5d; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #344870; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #5f8b3b; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: rgba(95, 139, 59, 0.73); + --vscode-extensionIcon-starForeground: #ff8e00; + --vscode-extensionIcon-verifiedForeground: #3794ff; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #d758b3; + --vscode-terminal-ansiBlack: #111111; + --vscode-terminal-ansiRed: #ff9da4; + --vscode-terminal-ansiGreen: #d1f1a9; + --vscode-terminal-ansiYellow: #ffeead; + --vscode-terminal-ansiBlue: #bbdaff; + --vscode-terminal-ansiMagenta: #ebbbff; + --vscode-terminal-ansiCyan: #99ffff; + --vscode-terminal-ansiWhite: #cccccc; + --vscode-terminal-ansiBrightBlack: #333333; + --vscode-terminal-ansiBrightRed: #ff7882; + --vscode-terminal-ansiBrightGreen: #b8f171; + --vscode-terminal-ansiBrightYellow: #ffe580; + --vscode-terminal-ansiBrightBlue: #80baff; + --vscode-terminal-ansiBrightMagenta: #d778ff; + --vscode-terminal-ansiBrightCyan: #78ffff; + --vscode-terminal-ansiBrightWhite: #ffffff; + --vscode-interactive-activeCodeBorder: #2b2b4a; + --vscode-interactive-inactiveCodeBorder: #152037; + --vscode-gitDecoration-addedResourceForeground: #81b88b; + --vscode-gitDecoration-modifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-deletedResourceForeground: #c74e39; + --vscode-gitDecoration-renamedResourceForeground: #73c991; + --vscode-gitDecoration-untrackedResourceForeground: #73c991; + --vscode-gitDecoration-ignoredResourceForeground: #8c8c8c; + --vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-stageDeletedResourceForeground: #c74e39; + --vscode-gitDecoration-conflictingResourceForeground: #e4676b; + --vscode-gitDecoration-submoduleResourceForeground: #8db9e2; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/dark-ayu-mirage.scss b/vendor/mynah-ui/example/src/styles/themes/dark-ayu-mirage.scss new file mode 100644 index 00000000..84288fdc --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/dark-ayu-mirage.scss @@ -0,0 +1,638 @@ +html[theme='dark-ayu-mirage']:root { + --vscode-foreground: #707a8c; + --vscode-disabledForeground: rgba(204, 204, 204, 0.5); + --vscode-errorForeground: #ff6666; + --vscode-descriptionForeground: #707a8c; + --vscode-icon-foreground: #707a8c; + --vscode-focusBorder: rgba(255, 204, 102, 0.7); + --vscode-selection-background: rgba(64, 159, 255, 0.25); + --vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18); + --vscode-textLink-foreground: #ffcc66; + --vscode-textLink-activeForeground: #ffcc66; + --vscode-textPreformat-foreground: #cccac2; + --vscode-textBlockQuote-background: #1c212b; + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4); + --vscode-widget-shadow: rgba(18, 21, 28, 0.7); + --vscode-input-background: #242936; + --vscode-input-foreground: #cccac2; + --vscode-input-border: rgba(112, 122, 140, 0.27); + --vscode-inputOption-activeBorder: rgba(255, 204, 102, 0.3); + --vscode-inputOption-hoverBackground: rgba(90, 93, 94, 0.5); + --vscode-inputOption-activeBackground: rgba(255, 204, 102, 0.2); + --vscode-inputOption-activeForeground: #ffcc66; + --vscode-input-placeholderForeground: rgba(112, 122, 140, 0.5); + --vscode-inputValidation-infoBackground: #1f2430; + --vscode-inputValidation-infoBorder: #5ccfe6; + --vscode-inputValidation-warningBackground: #1f2430; + --vscode-inputValidation-warningBorder: #ffd173; + --vscode-inputValidation-errorBackground: #242936; + --vscode-inputValidation-errorBorder: #ff6666; + --vscode-dropdown-background: #242936; + --vscode-dropdown-foreground: #707a8c; + --vscode-dropdown-border: rgba(112, 122, 140, 0.27); + --vscode-button-foreground: #805500; + --vscode-button-separator: rgba(128, 85, 0, 0.4); + --vscode-button-background: #ffcc66; + --vscode-button-hoverBackground: #fac761; + --vscode-button-secondaryForeground: #cccac2; + --vscode-button-secondaryBackground: rgba(112, 122, 140, 0.2); + --vscode-button-secondaryHoverBackground: rgba(112, 122, 140, 0.5); + --vscode-badge-background: rgba(255, 204, 102, 0.2); + --vscode-badge-foreground: #ffcc66; + --vscode-scrollbar-shadow: rgba(23, 27, 36, 0); + --vscode-scrollbarSlider-background: rgba(112, 122, 140, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(112, 122, 140, 0.6); + --vscode-scrollbarSlider-activeBackground: rgba(112, 122, 140, 0.7); + --vscode-progressBar-background: #ffcc66; + --vscode-editorError-foreground: #ff6666; + --vscode-editorWarning-foreground: #ffcc66; + --vscode-editorInfo-foreground: #3794ff; + --vscode-editorHint-foreground: rgba(238, 238, 238, 0.7); + --vscode-sash-hoverBorder: rgba(255, 204, 102, 0.7); + --vscode-editor-background: #242936; + --vscode-editor-foreground: #cccac2; + --vscode-editorStickyScroll-background: #242936; + --vscode-editorStickyScrollHover-background: #2a2d2e; + --vscode-editorWidget-background: #1f2430; + --vscode-editorWidget-foreground: #707a8c; + --vscode-editorWidget-border: #171b24; + --vscode-quickInput-background: #1f2430; + --vscode-quickInput-foreground: #707a8c; + --vscode-quickInputTitle-background: rgba(255, 255, 255, 0.1); + --vscode-pickerGroup-foreground: rgba(112, 122, 140, 0.5); + --vscode-pickerGroup-border: #171b24; + --vscode-keybindingLabel-background: rgba(112, 122, 140, 0.1); + --vscode-keybindingLabel-foreground: #cccac2; + --vscode-keybindingLabel-border: rgba(204, 202, 194, 0.1); + --vscode-keybindingLabel-bottomBorder: rgba(204, 202, 194, 0.1); + --vscode-editor-selectionBackground: rgba(64, 159, 255, 0.25); + --vscode-editor-inactiveSelectionBackground: rgba(64, 159, 255, 0.13); + --vscode-editor-selectionHighlightBackground: rgba(135, 217, 108, 0.15); + --vscode-editor-selectionHighlightBorder: rgba(135, 217, 108, 0); + --vscode-editor-findMatchBackground: #695380; + --vscode-editor-findMatchHighlightBackground: rgba(105, 83, 128, 0.4); + --vscode-editor-findRangeHighlightBackground: rgba(105, 83, 128, 0.25); + --vscode-editor-findMatchBorder: #695380; + --vscode-editor-findMatchHighlightBorder: rgba(92, 70, 114, 0.4); + --vscode-searchEditor-findMatchBackground: rgba(105, 83, 128, 0.26); + --vscode-searchEditor-findMatchBorder: rgba(92, 70, 114, 0.26); + --vscode-search-resultsInfoForeground: rgba(112, 122, 140, 0.65); + --vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25); + --vscode-editorHoverWidget-background: #1f2430; + --vscode-editorHoverWidget-foreground: #707a8c; + --vscode-editorHoverWidget-border: #171b24; + --vscode-editorHoverWidget-statusBarBackground: #252b3a; + --vscode-editorLink-activeForeground: #ffcc66; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(255, 204, 102, 0.02); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(255, 204, 102, 0.02); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(255, 204, 102, 0.02); + --vscode-editorLightBulb-foreground: #ffcc00; + --vscode-editorLightBulbAutoFix-foreground: #75beff; + --vscode-diffEditor-insertedTextBackground: rgba(135, 217, 108, 0.12); + --vscode-diffEditor-removedTextBackground: rgba(242, 121, 131, 0.12); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: #171b24; + --vscode-diffEditor-unchangedRegionBackground: #3e3e3e; + --vscode-diffEditor-unchangedRegionForeground: #a3a2a2; + --vscode-diffEditor-unchangedCodeBackground: rgba(116, 116, 116, 0.16); + --vscode-list-focusBackground: rgba(99, 117, 153, 0.15); + --vscode-list-focusForeground: #cccac2; + --vscode-list-focusOutline: rgba(99, 117, 153, 0.15); + --vscode-list-activeSelectionBackground: rgba(99, 117, 153, 0.15); + --vscode-list-activeSelectionForeground: #cccac2; + --vscode-list-inactiveSelectionBackground: rgba(105, 117, 140, 0.12); + --vscode-list-inactiveSelectionForeground: #707a8c; + --vscode-list-hoverBackground: rgba(99, 117, 153, 0.15); + --vscode-list-dropBackground: #062f4a; + --vscode-list-highlightForeground: #ffcc66; + --vscode-list-focusHighlightForeground: #ffcc66; + --vscode-list-invalidItemForeground: rgba(112, 122, 140, 0.3); + --vscode-list-errorForeground: #ff6666; + --vscode-list-warningForeground: #cca700; + --vscode-listFilterWidget-background: #1c212b; + --vscode-listFilterWidget-outline: #ffcc66; + --vscode-listFilterWidget-noMatchesOutline: #ff6666; + --vscode-listFilterWidget-shadow: rgba(18, 21, 28, 0.7); + --vscode-list-filterMatchBackground: rgba(92, 70, 114, 0.4); + --vscode-list-filterMatchBorder: rgba(105, 83, 128, 0.4); + --vscode-tree-indentGuidesStroke: rgba(138, 145, 153, 0.35); + --vscode-tree-inactiveIndentGuidesStroke: rgba(138, 145, 153, 0.14); + --vscode-tree-tableColumnsBorder: rgba(204, 204, 204, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(112, 122, 140, 0.04); + --vscode-list-deemphasizedForeground: #ff6666; + --vscode-checkbox-background: #242936; + --vscode-checkbox-selectBackground: #1f2430; + --vscode-checkbox-foreground: #707a8c; + --vscode-checkbox-border: rgba(112, 122, 140, 0.27); + --vscode-checkbox-selectBorder: #707a8c; + --vscode-quickInputList-focusForeground: #cccac2; + --vscode-quickInputList-focusBackground: rgba(99, 117, 153, 0.15); + --vscode-menu-foreground: #707a8c; + --vscode-menu-background: #242936; + --vscode-menu-selectionForeground: #cccac2; + --vscode-menu-selectionBackground: rgba(99, 117, 153, 0.15); + --vscode-menu-separatorBackground: #606060; + --vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31); + --vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(135, 217, 108, 0.2); + --vscode-editor-snippetFinalTabstopHighlightBorder: #525252; + --vscode-breadcrumb-foreground: rgba(112, 122, 140, 0.8); + --vscode-breadcrumb-background: #242936; + --vscode-breadcrumb-focusForeground: #7e8797; + --vscode-breadcrumb-activeSelectionForeground: #7e8797; + --vscode-breadcrumbPicker-background: #1f2430; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: #695380; + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #695380; + --vscode-minimap-selectionOccurrenceHighlight: #676767; + --vscode-minimap-selectionHighlight: rgba(64, 159, 255, 0.25); + --vscode-minimap-errorHighlight: #ff6666; + --vscode-minimap-warningHighlight: #ffcc66; + --vscode-minimap-background: #242936; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(112, 122, 140, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(112, 122, 140, 0.3); + --vscode-minimapSlider-activeBackground: rgba(112, 122, 140, 0.35); + --vscode-problemsErrorIcon-foreground: #ff6666; + --vscode-problemsWarningIcon-foreground: #ffcc66; + --vscode-problemsInfoIcon-foreground: #3794ff; + --vscode-charts-foreground: #707a8c; + --vscode-charts-lines: rgba(112, 122, 140, 0.5); + --vscode-charts-red: #ff6666; + --vscode-charts-blue: #3794ff; + --vscode-charts-yellow: #ffcc66; + --vscode-charts-orange: #695380; + --vscode-charts-green: #89d185; + --vscode-charts-purple: #b180d7; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #707a8c; + --vscode-symbolIcon-booleanForeground: #707a8c; + --vscode-symbolIcon-classForeground: #ee9d28; + --vscode-symbolIcon-colorForeground: #707a8c; + --vscode-symbolIcon-constantForeground: #707a8c; + --vscode-symbolIcon-constructorForeground: #b180d7; + --vscode-symbolIcon-enumeratorForeground: #ee9d28; + --vscode-symbolIcon-enumeratorMemberForeground: #75beff; + --vscode-symbolIcon-eventForeground: #ee9d28; + --vscode-symbolIcon-fieldForeground: #75beff; + --vscode-symbolIcon-fileForeground: #707a8c; + --vscode-symbolIcon-folderForeground: #707a8c; + --vscode-symbolIcon-functionForeground: #b180d7; + --vscode-symbolIcon-interfaceForeground: #75beff; + --vscode-symbolIcon-keyForeground: #707a8c; + --vscode-symbolIcon-keywordForeground: #707a8c; + --vscode-symbolIcon-methodForeground: #b180d7; + --vscode-symbolIcon-moduleForeground: #707a8c; + --vscode-symbolIcon-namespaceForeground: #707a8c; + --vscode-symbolIcon-nullForeground: #707a8c; + --vscode-symbolIcon-numberForeground: #707a8c; + --vscode-symbolIcon-objectForeground: #707a8c; + --vscode-symbolIcon-operatorForeground: #707a8c; + --vscode-symbolIcon-packageForeground: #707a8c; + --vscode-symbolIcon-propertyForeground: #707a8c; + --vscode-symbolIcon-referenceForeground: #707a8c; + --vscode-symbolIcon-snippetForeground: #707a8c; + --vscode-symbolIcon-stringForeground: #707a8c; + --vscode-symbolIcon-structForeground: #707a8c; + --vscode-symbolIcon-textForeground: #707a8c; + --vscode-symbolIcon-typeParameterForeground: #707a8c; + --vscode-symbolIcon-unitForeground: #707a8c; + --vscode-symbolIcon-variableForeground: #75beff; + --vscode-actionBar-toggledBackground: rgba(255, 204, 102, 0.2); + --vscode-editorHoverWidget-highlightForeground: #ffcc66; + --vscode-editor-lineHighlightBackground: #1a1f29; + --vscode-editor-lineHighlightBorder: #282828; + --vscode-editor-rangeHighlightBackground: rgba(105, 83, 128, 0.2); + --vscode-editor-symbolHighlightBackground: rgba(105, 83, 128, 0.4); + --vscode-editorCursor-foreground: #ffcc66; + --vscode-editorWhitespace-foreground: rgba(138, 145, 153, 0.4); + --vscode-editorLineNumber-foreground: rgba(138, 145, 153, 0.4); + --vscode-editorIndentGuide-background: rgba(138, 145, 153, 0.18); + --vscode-editorIndentGuide-activeBackground: rgba(138, 145, 153, 0.35); + --vscode-editorIndentGuide-background1: rgba(138, 145, 153, 0.18); + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: rgba(138, 145, 153, 0.35); + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #c6c6c6; + --vscode-editorLineNumber-activeForeground: rgba(138, 145, 153, 0.8); + --vscode-editorRuler-foreground: rgba(138, 145, 153, 0.18); + --vscode-editorCodeLens-foreground: rgba(184, 207, 230, 0.5); + --vscode-editorBracketMatch-background: rgba(138, 145, 153, 0.3); + --vscode-editorBracketMatch-border: rgba(138, 145, 153, 0.3); + --vscode-editorOverviewRuler-border: #171b24; + --vscode-editorGutter-background: #242936; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67); + --vscode-editorGhostText-foreground: rgba(255, 255, 255, 0.34); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: #ff6666; + --vscode-editorOverviewRuler-warningForeground: #ffcc66; + --vscode-editorOverviewRuler-infoForeground: #3794ff; + --vscode-editorBracketHighlight-foreground1: #ffd700; + --vscode-editorBracketHighlight-foreground2: #da70d6; + --vscode-editorBracketHighlight-foreground3: #179fff; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #bd9b03; + --vscode-editorUnicodeHighlight-background: rgba(189, 155, 3, 0.15); + --vscode-editorOverviewRuler-bracketMatchForeground: rgba(138, 145, 153, 0.7); + --vscode-editor-foldBackground: rgba(64, 159, 255, 0.07); + --vscode-editorGutter-foldingControlForeground: #707a8c; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(128, 191, 255, 0.08); + --vscode-editor-wordHighlightStrongBackground: rgba(135, 217, 108, 0.08); + --vscode-editor-wordHighlightTextBackground: rgba(128, 191, 255, 0.08); + --vscode-editor-wordHighlightBorder: rgba(128, 191, 255, 0.5); + --vscode-editor-wordHighlightStrongBorder: rgba(135, 217, 108, 0.5); + --vscode-editor-wordHighlightTextBorder: rgba(128, 191, 255, 0.5); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(128, 191, 255, 0.4); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(135, 217, 108, 0.4); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: rgba(99, 117, 153, 0.15); + --vscode-peekViewTitleLabel-foreground: #cccac2; + --vscode-peekViewTitleDescription-foreground: #707a8c; + --vscode-peekView-border: rgba(99, 117, 153, 0.15); + --vscode-peekViewResult-background: #1f2430; + --vscode-peekViewResult-lineForeground: #707a8c; + --vscode-peekViewResult-fileForeground: #cccac2; + --vscode-peekViewResult-selectionBackground: rgba(99, 117, 153, 0.15); + --vscode-peekViewResult-selectionForeground: #ffffff; + --vscode-peekViewEditor-background: #1f2430; + --vscode-peekViewEditorGutter-background: #1f2430; + --vscode-peekViewEditorStickyScroll-background: #1f2430; + --vscode-peekViewResult-matchHighlightBackground: rgba(105, 83, 128, 0.4); + --vscode-peekViewEditor-matchHighlightBackground: rgba(105, 83, 128, 0.4); + --vscode-peekViewEditor-matchHighlightBorder: rgba(92, 70, 114, 0.4); + --vscode-editorMarkerNavigationError-background: #ff6666; + --vscode-editorMarkerNavigationError-headerBackground: rgba(255, 102, 102, 0.1); + --vscode-editorMarkerNavigationWarning-background: #ffcc66; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(255, 204, 102, 0.1); + --vscode-editorMarkerNavigationInfo-background: #3794ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, 0.1); + --vscode-editorMarkerNavigation-background: #1c212b; + --vscode-editorSuggestWidget-background: #1c212b; + --vscode-editorSuggestWidget-border: #171b24; + --vscode-editorSuggestWidget-foreground: #cccac2; + --vscode-editorSuggestWidget-selectedForeground: #cccac2; + --vscode-editorSuggestWidget-selectedBackground: rgba(99, 117, 153, 0.15); + --vscode-editorSuggestWidget-highlightForeground: #ffcc66; + --vscode-editorSuggestWidget-focusHighlightForeground: #ffcc66; + --vscode-editorSuggestWidgetStatus-foreground: rgba(204, 202, 194, 0.5); + --vscode-tab-activeBackground: #242936; + --vscode-tab-unfocusedActiveBackground: #242936; + --vscode-tab-inactiveBackground: #1f2430; + --vscode-tab-unfocusedInactiveBackground: #1f2430; + --vscode-tab-activeForeground: #cccac2; + --vscode-tab-inactiveForeground: #707a8c; + --vscode-tab-unfocusedActiveForeground: #707a8c; + --vscode-tab-unfocusedInactiveForeground: #707a8c; + --vscode-tab-border: #171b24; + --vscode-tab-lastPinnedBorder: rgba(138, 145, 153, 0.35); + --vscode-tab-activeBorder: #242936; + --vscode-tab-unfocusedActiveBorder: rgba(36, 41, 54, 0.5); + --vscode-tab-activeBorderTop: #ffcc66; + --vscode-tab-unfocusedActiveBorderTop: #707a8c; + --vscode-tab-activeModifiedBorder: #3399cc; + --vscode-tab-inactiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 153, 204, 0.25); + --vscode-editorPane-background: #242936; + --vscode-editorGroupHeader-tabsBackground: #1f2430; + --vscode-editorGroupHeader-tabsBorder: #171b24; + --vscode-editorGroupHeader-noTabsBackground: #1f2430; + --vscode-editorGroup-border: #171b24; + --vscode-editorGroup-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-editorGroup-dropIntoPromptForeground: #707a8c; + --vscode-editorGroup-dropIntoPromptBackground: #1f2430; + --vscode-sideBySideEditor-horizontalBorder: #171b24; + --vscode-sideBySideEditor-verticalBorder: #171b24; + --vscode-panel-background: #1f2430; + --vscode-panel-border: #171b24; + --vscode-panelTitle-activeForeground: #cccac2; + --vscode-panelTitle-inactiveForeground: #707a8c; + --vscode-panelTitle-activeBorder: #ffcc66; + --vscode-panelInput-border: rgba(112, 122, 140, 0.27); + --vscode-panel-dropBorder: #cccac2; + --vscode-panelSection-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: #171b24; + --vscode-banner-background: rgba(99, 117, 153, 0.15); + --vscode-banner-foreground: #cccac2; + --vscode-banner-iconForeground: #3794ff; + --vscode-statusBar-foreground: #707a8c; + --vscode-statusBar-noFolderForeground: #707a8c; + --vscode-statusBar-background: #1f2430; + --vscode-statusBar-noFolderBackground: #1c212b; + --vscode-statusBar-border: #171b24; + --vscode-statusBar-focusBorder: #707a8c; + --vscode-statusBar-noFolderBorder: #171b24; + --vscode-statusBarItem-activeBackground: rgba(112, 122, 140, 0.2); + --vscode-statusBarItem-focusBorder: #707a8c; + --vscode-statusBarItem-hoverBackground: rgba(112, 122, 140, 0.2); + --vscode-statusBarItem-hoverForeground: #707a8c; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #707a8c; + --vscode-statusBarItem-prominentBackground: #171b24; + --vscode-statusBarItem-prominentHoverForeground: #707a8c; + --vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.19); + --vscode-statusBarItem-errorBackground: #d60000; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #707a8c; + --vscode-statusBarItem-errorHoverBackground: rgba(112, 122, 140, 0.2); + --vscode-statusBarItem-warningBackground: #d68f00; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #707a8c; + --vscode-statusBarItem-warningHoverBackground: rgba(112, 122, 140, 0.2); + --vscode-activityBar-background: #242936; + --vscode-activityBar-foreground: rgba(112, 122, 140, 0.8); + --vscode-activityBar-inactiveForeground: rgba(112, 122, 140, 0.6); + --vscode-activityBar-border: #171b24; + --vscode-activityBar-activeBorder: #ffcc66; + --vscode-activityBar-dropBorder: rgba(112, 122, 140, 0.8); + --vscode-activityBarBadge-background: #ffcc66; + --vscode-activityBarBadge-foreground: #805500; + --vscode-profileBadge-background: #4d4d4d; + --vscode-profileBadge-foreground: #ffffff; + --vscode-statusBarItem-remoteBackground: #ffcc66; + --vscode-statusBarItem-remoteForeground: #805500; + --vscode-statusBarItem-remoteHoverForeground: #707a8c; + --vscode-statusBarItem-remoteHoverBackground: rgba(112, 122, 140, 0.2); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #805500; + --vscode-statusBarItem-offlineHoverForeground: #707a8c; + --vscode-statusBarItem-offlineHoverBackground: rgba(112, 122, 140, 0.2); + --vscode-extensionBadge-remoteBackground: #ffcc66; + --vscode-extensionBadge-remoteForeground: #805500; + --vscode-sideBar-background: #1f2430; + --vscode-sideBar-border: #171b24; + --vscode-sideBarTitle-foreground: #707a8c; + --vscode-sideBar-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-sideBarSectionHeader-background: #1f2430; + --vscode-sideBarSectionHeader-foreground: #707a8c; + --vscode-sideBarSectionHeader-border: #171b24; + --vscode-titleBar-activeForeground: #cccac2; + --vscode-titleBar-inactiveForeground: #707a8c; + --vscode-titleBar-activeBackground: #1f2430; + --vscode-titleBar-inactiveBackground: #1f2430; + --vscode-titleBar-border: #171b24; + --vscode-menubar-selectionForeground: #cccac2; + --vscode-menubar-selectionBackground: rgba(90, 93, 94, 0.31); + --vscode-notifications-foreground: #707a8c; + --vscode-notifications-background: #1f2430; + --vscode-notificationLink-foreground: #ffcc66; + --vscode-notificationCenterHeader-background: #282f3f; + --vscode-notifications-border: #282f3f; + --vscode-notificationsErrorIcon-foreground: #ff6666; + --vscode-notificationsWarningIcon-foreground: #ffcc66; + --vscode-notificationsInfoIcon-foreground: #3794ff; + --vscode-commandCenter-foreground: #cccac2; + --vscode-commandCenter-activeForeground: #cccac2; + --vscode-commandCenter-inactiveForeground: #707a8c; + --vscode-commandCenter-background: rgba(255, 255, 255, 0.05); + --vscode-commandCenter-activeBackground: rgba(255, 255, 255, 0.08); + --vscode-commandCenter-border: rgba(204, 202, 194, 0.2); + --vscode-commandCenter-activeBorder: rgba(204, 202, 194, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(112, 122, 140, 0.25); + --vscode-chat-requestBorder: rgba(255, 255, 255, 0.1); + --vscode-chat-slashCommandBackground: rgba(255, 204, 102, 0.2); + --vscode-chat-slashCommandForeground: #ffcc66; + --vscode-simpleFindWidget-sashBorder: #454545; + --vscode-commentsView-resolvedIcon: rgba(204, 204, 204, 0.5); + --vscode-commentsView-unresolvedIcon: rgba(99, 117, 153, 0.15); + --vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: rgba(99, 117, 153, 0.15); + --vscode-editorCommentsWidget-rangeBackground: rgba(99, 117, 153, 0.01); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(99, 117, 153, 0.01); + --vscode-editorGutter-commentRangeForeground: #2c3240; + --vscode-editorOverviewRuler-commentForeground: #2c3240; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #2c3240; + --vscode-editorGutter-commentGlyphForeground: #cccac2; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #cccac2; + --vscode-debugToolBar-background: #1c212b; + --vscode-debugIcon-startForeground: #89d185; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.2); + --vscode-mergeEditor-changeBase\.background: #4b1818; + --vscode-mergeEditor-changeBase\.word\.background: #6f1313; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, 0.48); + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #cccac2; + --vscode-settings-settingsHeaderHoverForeground: rgba(204, 202, 194, 0.7); + --vscode-settings-modifiedItemIndicator: #80bfff; + --vscode-settings-headerBorder: #171b24; + --vscode-settings-sashBorder: #171b24; + --vscode-settings-dropdownBackground: #242936; + --vscode-settings-dropdownForeground: #707a8c; + --vscode-settings-dropdownBorder: rgba(112, 122, 140, 0.27); + --vscode-settings-dropdownListBorder: #171b24; + --vscode-settings-checkboxBackground: #242936; + --vscode-settings-checkboxForeground: #707a8c; + --vscode-settings-checkboxBorder: rgba(112, 122, 140, 0.27); + --vscode-settings-textInputBackground: #242936; + --vscode-settings-textInputForeground: #cccac2; + --vscode-settings-textInputBorder: rgba(112, 122, 140, 0.27); + --vscode-settings-numberInputBackground: #242936; + --vscode-settings-numberInputForeground: #cccac2; + --vscode-settings-numberInputBorder: rgba(112, 122, 140, 0.27); + --vscode-settings-focusedRowBackground: rgba(99, 117, 153, 0.09); + --vscode-settings-rowHoverBackground: rgba(99, 117, 153, 0.04); + --vscode-settings-focusedRowBorder: rgba(255, 204, 102, 0.7); + --vscode-terminal-background: #1f2430; + --vscode-terminal-foreground: #cccac2; + --vscode-terminal-selectionBackground: rgba(64, 159, 255, 0.25); + --vscode-terminal-inactiveSelectionBackground: rgba(64, 159, 255, 0.13); + --vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, 0.25); + --vscode-terminalCommandDecoration-successBackground: #1b81a8; + --vscode-terminalCommandDecoration-errorBackground: #f14c4c; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: #171b24; + --vscode-terminal-findMatchBackground: #695380; + --vscode-terminal-hoverHighlightBackground: rgba(38, 79, 120, 0.13); + --vscode-terminal-findMatchHighlightBackground: rgba(105, 83, 128, 0.4); + --vscode-terminalOverviewRuler-findMatchForeground: #695380; + --vscode-terminal-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-terminal-tab\.activeBorder: #242936; + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #ff6666; + --vscode-testing-peekHeaderBackground: rgba(255, 102, 102, 0.1); + --vscode-testing-message\.error\.decorationForeground: #ff6666; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(204, 202, 194, 0.5); + --vscode-welcomePage-tileBackground: #1f2430; + --vscode-welcomePage-tileHoverBackground: #252b3a; + --vscode-welcomePage-tileBorder: rgba(255, 255, 255, 0.1); + --vscode-welcomePage-progress\.background: #1a1f29; + --vscode-welcomePage-progress\.foreground: #ffcc66; + --vscode-walkthrough-stepTitle\.foreground: #ffffff; + --vscode-walkThrough-embeddedEditorBackground: #1c212b; + --vscode-inlineChat-background: #1f2430; + --vscode-inlineChat-border: #171b24; + --vscode-inlineChat-shadow: rgba(18, 21, 28, 0.7); + --vscode-inlineChat-regionHighlight: rgba(38, 79, 120, 0.25); + --vscode-inlineChatInput-border: #171b24; + --vscode-inlineChatInput-focusBorder: rgba(255, 204, 102, 0.7); + --vscode-inlineChatInput-placeholderForeground: rgba(112, 122, 140, 0.5); + --vscode-inlineChatInput-background: #242936; + --vscode-inlineChatDiff-inserted: rgba(135, 217, 108, 0.06); + --vscode-inlineChatDiff-removed: rgba(242, 121, 131, 0.06); + --vscode-debugExceptionWidget-border: #171b24; + --vscode-debugExceptionWidget-background: #1c212b; + --vscode-ports-iconRunningProcessForeground: #ffcc66; + --vscode-statusBar-debuggingBackground: #f29e74; + --vscode-statusBar-debuggingForeground: #242936; + --vscode-statusBar-debuggingBorder: #171b24; + --vscode-editor-inlineValuesForeground: rgba(255, 255, 255, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: rgba(128, 191, 255, 0.8); + --vscode-editorGutter-addedBackground: rgba(135, 217, 108, 0.8); + --vscode-editorGutter-deletedBackground: rgba(242, 121, 131, 0.8); + --vscode-minimapGutter-modifiedBackground: #80bfff; + --vscode-minimapGutter-addedBackground: #87d96c; + --vscode-minimapGutter-deletedBackground: #f27983; + --vscode-editorOverviewRuler-modifiedForeground: #80bfff; + --vscode-editorOverviewRuler-addedForeground: #87d96c; + --vscode-editorOverviewRuler-deletedForeground: #f27983; + --vscode-debugIcon-breakpointForeground: #f29e74; + --vscode-debugIcon-breakpointDisabledForeground: rgba(242, 158, 116, 0.5); + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: rgba(105, 117, 140, 0.12); + --vscode-notebook-focusedEditorBorder: rgba(255, 204, 102, 0.7); + --vscode-notebookStatusSuccessIcon-foreground: #89d185; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #89d185; + --vscode-notebookStatusErrorIcon-foreground: #ff6666; + --vscode-notebookStatusRunningIcon-foreground: #707a8c; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: rgba(105, 117, 140, 0.12); + --vscode-notebook-selectedCellBorder: rgba(105, 117, 140, 0.12); + --vscode-notebook-focusedCellBorder: rgba(255, 204, 102, 0.7); + --vscode-notebook-inactiveFocusedCellBorder: rgba(105, 117, 140, 0.12); + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, 0.15); + --vscode-notebook-cellInsertionIndicator: rgba(255, 204, 102, 0.7); + --vscode-notebookScrollbarSlider-background: rgba(112, 122, 140, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(112, 122, 140, 0.6); + --vscode-notebookScrollbarSlider-activeBackground: rgba(112, 122, 140, 0.7); + --vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-notebook-cellEditorBackground: #1f2430; + --vscode-notebook-editorBackground: #242936; + --vscode-keybindingTable-headerBackground: rgba(112, 122, 140, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(112, 122, 140, 0.04); + --vscode-searchEditor-textInputBorder: rgba(112, 122, 140, 0.27); + --vscode-debugTokenExpression-name: #c586c0; + --vscode-debugTokenExpression-value: rgba(204, 204, 204, 0.6); + --vscode-debugTokenExpression-string: #ce9178; + --vscode-debugTokenExpression-boolean: #4e94ce; + --vscode-debugTokenExpression-number: #b5cea8; + --vscode-debugTokenExpression-error: #f48771; + --vscode-debugView-exceptionLabelForeground: #707a8c; + --vscode-debugView-exceptionLabelBackground: #6c2022; + --vscode-debugView-stateLabelForeground: #707a8c; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #3794ff; + --vscode-debugConsole-warningForeground: #ffcc66; + --vscode-debugConsole-errorForeground: #ff6666; + --vscode-debugConsole-sourceForeground: #707a8c; + --vscode-debugConsoleInputIcon-foreground: #ffcc66; + --vscode-debugIcon-pauseForeground: #75beff; + --vscode-debugIcon-stopForeground: #f48771; + --vscode-debugIcon-disconnectForeground: #f48771; + --vscode-debugIcon-restartForeground: #89d185; + --vscode-debugIcon-stepOverForeground: #75beff; + --vscode-debugIcon-stepIntoForeground: #75beff; + --vscode-debugIcon-stepOutForeground: #75beff; + --vscode-debugIcon-continueForeground: #75beff; + --vscode-debugIcon-stepBackForeground: #75beff; + --vscode-scm-providerBorder: #454545; + --vscode-extensionButton-background: #ffcc66; + --vscode-extensionButton-foreground: #805500; + --vscode-extensionButton-hoverBackground: #fac761; + --vscode-extensionButton-separator: rgba(128, 85, 0, 0.4); + --vscode-extensionButton-prominentBackground: #ffcc66; + --vscode-extensionButton-prominentForeground: #805500; + --vscode-extensionButton-prominentHoverBackground: #fac761; + --vscode-extensionIcon-starForeground: #ff8e00; + --vscode-extensionIcon-verifiedForeground: #ffcc66; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #d758b3; + --vscode-terminal-ansiBlack: #171b24; + --vscode-terminal-ansiRed: #ed8274; + --vscode-terminal-ansiGreen: #87d96c; + --vscode-terminal-ansiYellow: #facc6e; + --vscode-terminal-ansiBlue: #6dcbfa; + --vscode-terminal-ansiMagenta: #dabafa; + --vscode-terminal-ansiCyan: #90e1c6; + --vscode-terminal-ansiWhite: #c7c7c7; + --vscode-terminal-ansiBrightBlack: #686868; + --vscode-terminal-ansiBrightRed: #f28779; + --vscode-terminal-ansiBrightGreen: #d5ff80; + --vscode-terminal-ansiBrightYellow: #ffd173; + --vscode-terminal-ansiBrightBlue: #73d0ff; + --vscode-terminal-ansiBrightMagenta: #dfbfff; + --vscode-terminal-ansiBrightCyan: #95e6cb; + --vscode-terminal-ansiBrightWhite: #ffffff; + --vscode-interactive-activeCodeBorder: rgba(99, 117, 153, 0.15); + --vscode-interactive-inactiveCodeBorder: rgba(105, 117, 140, 0.12); + --vscode-gitDecoration-addedResourceForeground: #81b88b; + --vscode-gitDecoration-modifiedResourceForeground: rgba(128, 191, 255, 0.7); + --vscode-gitDecoration-deletedResourceForeground: rgba(242, 121, 131, 0.7); + --vscode-gitDecoration-renamedResourceForeground: #73c991; + --vscode-gitDecoration-untrackedResourceForeground: rgba(135, 217, 108, 0.7); + --vscode-gitDecoration-ignoredResourceForeground: rgba(112, 122, 140, 0.5); + --vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-stageDeletedResourceForeground: #c74e39; + --vscode-gitDecoration-conflictingResourceForeground: #ff0000; + --vscode-gitDecoration-submoduleResourceForeground: rgba(223, 191, 255, 0.7); +} diff --git a/vendor/mynah-ui/example/src/styles/themes/dark-dracula.scss b/vendor/mynah-ui/example/src/styles/themes/dark-dracula.scss new file mode 100644 index 00000000..6a47f156 --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/dark-dracula.scss @@ -0,0 +1,617 @@ +html[theme='dark-dracula']:root { + --vscode-foreground: #f8f8f2; + --vscode-disabledForeground: rgba(204, 204, 204, 0.5); + --vscode-errorForeground: #ff5555; + --vscode-descriptionForeground: rgba(248, 248, 242, 0.7); + --vscode-icon-foreground: #c5c5c5; + --vscode-focusBorder: #6272a4; + --vscode-selection-background: #bd93f9; + --vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18); + --vscode-textLink-foreground: #3794ff; + --vscode-textLink-activeForeground: #3794ff; + --vscode-textPreformat-foreground: #d7ba7d; + --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1); + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4); + --vscode-widget-shadow: rgba(0, 0, 0, 0.36); + --vscode-input-background: #282a36; + --vscode-input-foreground: #f8f8f2; + --vscode-input-border: #191a21; + --vscode-inputOption-activeBorder: #bd93f9; + --vscode-inputOption-hoverBackground: rgba(90, 93, 94, 0.5); + --vscode-inputOption-activeBackground: rgba(98, 114, 164, 0.4); + --vscode-inputOption-activeForeground: #ffffff; + --vscode-input-placeholderForeground: #6272a4; + --vscode-inputValidation-infoBackground: #063b49; + --vscode-inputValidation-infoBorder: #ff79c6; + --vscode-inputValidation-warningBackground: #352a05; + --vscode-inputValidation-warningBorder: #ffb86c; + --vscode-inputValidation-errorBackground: #5a1d1d; + --vscode-inputValidation-errorBorder: #ff5555; + --vscode-dropdown-background: #343746; + --vscode-dropdown-foreground: #f8f8f2; + --vscode-dropdown-border: #191a21; + --vscode-button-foreground: #f8f8f2; + --vscode-button-separator: rgba(248, 248, 242, 0.4); + --vscode-button-background: #44475a; + --vscode-button-hoverBackground: #52556c; + --vscode-button-secondaryForeground: #f8f8f2; + --vscode-button-secondaryBackground: #282a36; + --vscode-button-secondaryHoverBackground: #343746; + --vscode-badge-background: #44475a; + --vscode-badge-foreground: #f8f8f2; + --vscode-scrollbar-shadow: #000000; + --vscode-scrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-progressBar-background: #ff79c6; + --vscode-editorError-foreground: #ff5555; + --vscode-editorWarning-foreground: #8be9fd; + --vscode-editorInfo-foreground: #3794ff; + --vscode-editorHint-foreground: rgba(238, 238, 238, 0.7); + --vscode-sash-hoverBorder: #6272a4; + --vscode-editor-background: #282a36; + --vscode-editor-foreground: #f8f8f2; + --vscode-editorStickyScroll-background: #282a36; + --vscode-editorStickyScrollHover-background: #2a2d2e; + --vscode-editorWidget-background: #21222c; + --vscode-editorWidget-foreground: #f8f8f2; + --vscode-editorWidget-border: #454545; + --vscode-quickInput-background: #21222c; + --vscode-quickInput-foreground: #f8f8f2; + --vscode-quickInputTitle-background: rgba(255, 255, 255, 0.1); + --vscode-pickerGroup-foreground: #8be9fd; + --vscode-pickerGroup-border: #bd93f9; + --vscode-keybindingLabel-background: rgba(128, 128, 128, 0.17); + --vscode-keybindingLabel-foreground: #cccccc; + --vscode-keybindingLabel-border: rgba(51, 51, 51, 0.6); + --vscode-keybindingLabel-bottomBorder: rgba(68, 68, 68, 0.6); + --vscode-editor-selectionBackground: #44475a; + --vscode-editor-inactiveSelectionBackground: rgba(68, 71, 90, 0.5); + --vscode-editor-selectionHighlightBackground: #424450; + --vscode-editor-findMatchBackground: rgba(255, 184, 108, 0.5); + --vscode-editor-findMatchHighlightBackground: rgba(255, 255, 255, 0.25); + --vscode-editor-findRangeHighlightBackground: rgba(68, 71, 90, 0.46); + --vscode-searchEditor-findMatchBackground: rgba(255, 255, 255, 0.17); + --vscode-search-resultsInfoForeground: rgba(248, 248, 242, 0.65); + --vscode-editor-hoverHighlightBackground: rgba(139, 233, 253, 0.31); + --vscode-editorHoverWidget-background: #282a36; + --vscode-editorHoverWidget-foreground: #f8f8f2; + --vscode-editorHoverWidget-border: #6272a4; + --vscode-editorHoverWidget-statusBarBackground: #303241; + --vscode-editorLink-activeForeground: #8be9fd; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(68, 71, 90, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(68, 71, 90, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(68, 71, 90, 0.1); + --vscode-editorLightBulb-foreground: #ffcc00; + --vscode-editorLightBulbAutoFix-foreground: #75beff; + --vscode-diffEditor-insertedTextBackground: rgba(80, 250, 123, 0.13); + --vscode-diffEditor-removedTextBackground: rgba(255, 85, 85, 0.31); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(204, 204, 204, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #3e3e3e; + --vscode-diffEditor-unchangedRegionForeground: #a3a2a2; + --vscode-diffEditor-unchangedCodeBackground: rgba(116, 116, 116, 0.16); + --vscode-list-focusBackground: rgba(68, 71, 90, 0.46); + --vscode-list-focusOutline: #6272a4; + --vscode-list-activeSelectionBackground: #44475a; + --vscode-list-activeSelectionForeground: #f8f8f2; + --vscode-list-inactiveSelectionBackground: rgba(68, 71, 90, 0.46); + --vscode-list-hoverBackground: rgba(68, 71, 90, 0.46); + --vscode-list-dropBackground: #44475a; + --vscode-list-highlightForeground: #8be9fd; + --vscode-list-focusHighlightForeground: #8be9fd; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #ff5555; + --vscode-list-warningForeground: #ffb86c; + --vscode-listFilterWidget-background: #343746; + --vscode-listFilterWidget-outline: #424450; + --vscode-listFilterWidget-noMatchesOutline: #ff5555; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.36); + --vscode-list-filterMatchBackground: rgba(255, 255, 255, 0.25); + --vscode-tree-indentGuidesStroke: #585858; + --vscode-tree-inactiveIndentGuidesStroke: rgba(88, 88, 88, 0.4); + --vscode-tree-tableColumnsBorder: rgba(204, 204, 204, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(248, 248, 242, 0.04); + --vscode-list-deemphasizedForeground: #8c8c8c; + --vscode-checkbox-background: #343746; + --vscode-checkbox-selectBackground: #21222c; + --vscode-checkbox-foreground: #f8f8f2; + --vscode-checkbox-border: #191a21; + --vscode-checkbox-selectBorder: #c5c5c5; + --vscode-quickInputList-focusForeground: #f8f8f2; + --vscode-quickInputList-focusBackground: #44475a; + --vscode-menu-foreground: #f8f8f2; + --vscode-menu-background: #343746; + --vscode-menu-selectionForeground: #f8f8f2; + --vscode-menu-selectionBackground: #44475a; + --vscode-menu-separatorBackground: #606060; + --vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31); + --vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: #282a36; + --vscode-editor-snippetTabstopHighlightBorder: #6272a4; + --vscode-editor-snippetFinalTabstopHighlightBackground: #282a36; + --vscode-editor-snippetFinalTabstopHighlightBorder: #50fa7b; + --vscode-breadcrumb-foreground: #6272a4; + --vscode-breadcrumb-background: #282a36; + --vscode-breadcrumb-focusForeground: #f8f8f2; + --vscode-breadcrumb-activeSelectionForeground: #f8f8f2; + --vscode-breadcrumbPicker-background: #191a21; + --vscode-merge-currentHeaderBackground: rgba(80, 250, 123, 0.56); + --vscode-merge-currentContentBackground: rgba(80, 250, 123, 0.23); + --vscode-merge-incomingHeaderBackground: rgba(189, 147, 249, 0.56); + --vscode-merge-incomingContentBackground: rgba(189, 147, 249, 0.23); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: #50fa7b; + --vscode-editorOverviewRuler-incomingContentForeground: #bd93f9; + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: #ffb86c; + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #676767; + --vscode-minimap-selectionHighlight: #264f78; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #8be9fd; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(121, 121, 121, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(191, 191, 191, 0.2); + --vscode-problemsErrorIcon-foreground: #ff5555; + --vscode-problemsWarningIcon-foreground: #8be9fd; + --vscode-problemsInfoIcon-foreground: #3794ff; + --vscode-charts-foreground: #f8f8f2; + --vscode-charts-lines: rgba(248, 248, 242, 0.5); + --vscode-charts-red: #ff5555; + --vscode-charts-blue: #3794ff; + --vscode-charts-yellow: #8be9fd; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #89d185; + --vscode-charts-purple: #b180d7; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #f8f8f2; + --vscode-symbolIcon-booleanForeground: #f8f8f2; + --vscode-symbolIcon-classForeground: #ee9d28; + --vscode-symbolIcon-colorForeground: #f8f8f2; + --vscode-symbolIcon-constantForeground: #f8f8f2; + --vscode-symbolIcon-constructorForeground: #b180d7; + --vscode-symbolIcon-enumeratorForeground: #ee9d28; + --vscode-symbolIcon-enumeratorMemberForeground: #75beff; + --vscode-symbolIcon-eventForeground: #ee9d28; + --vscode-symbolIcon-fieldForeground: #75beff; + --vscode-symbolIcon-fileForeground: #f8f8f2; + --vscode-symbolIcon-folderForeground: #f8f8f2; + --vscode-symbolIcon-functionForeground: #b180d7; + --vscode-symbolIcon-interfaceForeground: #75beff; + --vscode-symbolIcon-keyForeground: #f8f8f2; + --vscode-symbolIcon-keywordForeground: #f8f8f2; + --vscode-symbolIcon-methodForeground: #b180d7; + --vscode-symbolIcon-moduleForeground: #f8f8f2; + --vscode-symbolIcon-namespaceForeground: #f8f8f2; + --vscode-symbolIcon-nullForeground: #f8f8f2; + --vscode-symbolIcon-numberForeground: #f8f8f2; + --vscode-symbolIcon-objectForeground: #f8f8f2; + --vscode-symbolIcon-operatorForeground: #f8f8f2; + --vscode-symbolIcon-packageForeground: #f8f8f2; + --vscode-symbolIcon-propertyForeground: #f8f8f2; + --vscode-symbolIcon-referenceForeground: #f8f8f2; + --vscode-symbolIcon-snippetForeground: #f8f8f2; + --vscode-symbolIcon-stringForeground: #f8f8f2; + --vscode-symbolIcon-structForeground: #f8f8f2; + --vscode-symbolIcon-textForeground: #f8f8f2; + --vscode-symbolIcon-typeParameterForeground: #f8f8f2; + --vscode-symbolIcon-unitForeground: #f8f8f2; + --vscode-symbolIcon-variableForeground: #75beff; + --vscode-actionBar-toggledBackground: rgba(98, 114, 164, 0.4); + --vscode-editorHoverWidget-highlightForeground: #8be9fd; + --vscode-editor-lineHighlightBorder: #44475a; + --vscode-editor-rangeHighlightBackground: rgba(189, 147, 249, 0.08); + --vscode-editor-symbolHighlightBackground: rgba(255, 255, 255, 0.25); + --vscode-editorCursor-foreground: #aeafad; + --vscode-editorWhitespace-foreground: rgba(255, 255, 255, 0.1); + --vscode-editorLineNumber-foreground: #6272a4; + --vscode-editorIndentGuide-background: rgba(255, 255, 255, 0.1); + --vscode-editorIndentGuide-activeBackground: rgba(255, 255, 255, 0.27); + --vscode-editorIndentGuide-background1: rgba(255, 255, 255, 0.1); + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: rgba(255, 255, 255, 0.27); + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #c6c6c6; + --vscode-editorLineNumber-activeForeground: #c6c6c6; + --vscode-editorRuler-foreground: rgba(255, 255, 255, 0.1); + --vscode-editorCodeLens-foreground: #6272a4; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #888888; + --vscode-editorOverviewRuler-border: #191a21; + --vscode-editorGutter-background: #282a36; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67); + --vscode-editorGhostText-foreground: rgba(255, 255, 255, 0.34); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 85, 85, 0.5); + --vscode-editorOverviewRuler-warningForeground: rgba(255, 184, 108, 0.5); + --vscode-editorOverviewRuler-infoForeground: rgba(139, 233, 253, 0.5); + --vscode-editorBracketHighlight-foreground1: #f8f8f2; + --vscode-editorBracketHighlight-foreground2: #ff79c6; + --vscode-editorBracketHighlight-foreground3: #8be9fd; + --vscode-editorBracketHighlight-foreground4: #50fa7b; + --vscode-editorBracketHighlight-foreground5: #bd93f9; + --vscode-editorBracketHighlight-foreground6: #ffb86c; + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: #ff5555; + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #bd9b03; + --vscode-editorUnicodeHighlight-background: rgba(189, 155, 3, 0.15); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(33, 34, 44, 0.5); + --vscode-editorGutter-foldingControlForeground: #c5c5c5; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(139, 233, 253, 0.31); + --vscode-editor-wordHighlightStrongBackground: rgba(80, 250, 123, 0.31); + --vscode-editor-wordHighlightTextBackground: rgba(139, 233, 253, 0.31); + --vscode-editorOverviewRuler-wordHighlightForeground: #8be9fd; + --vscode-editorOverviewRuler-wordHighlightStrongForeground: #50fa7b; + --vscode-editorOverviewRuler-wordHighlightTextForeground: #ffb86c; + --vscode-peekViewTitle-background: #191a21; + --vscode-peekViewTitleLabel-foreground: #f8f8f2; + --vscode-peekViewTitleDescription-foreground: #6272a4; + --vscode-peekView-border: #44475a; + --vscode-peekViewResult-background: #21222c; + --vscode-peekViewResult-lineForeground: #f8f8f2; + --vscode-peekViewResult-fileForeground: #f8f8f2; + --vscode-peekViewResult-selectionBackground: #44475a; + --vscode-peekViewResult-selectionForeground: #f8f8f2; + --vscode-peekViewEditor-background: #282a36; + --vscode-peekViewEditorGutter-background: #282a36; + --vscode-peekViewEditorStickyScroll-background: #282a36; + --vscode-peekViewResult-matchHighlightBackground: rgba(241, 250, 140, 0.5); + --vscode-peekViewEditor-matchHighlightBackground: rgba(241, 250, 140, 0.5); + --vscode-editorMarkerNavigationError-background: #ff5555; + --vscode-editorMarkerNavigationError-headerBackground: rgba(255, 85, 85, 0.1); + --vscode-editorMarkerNavigationWarning-background: #8be9fd; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(139, 233, 253, 0.1); + --vscode-editorMarkerNavigationInfo-background: #3794ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, 0.1); + --vscode-editorMarkerNavigation-background: #21222c; + --vscode-editorSuggestWidget-background: #21222c; + --vscode-editorSuggestWidget-border: #454545; + --vscode-editorSuggestWidget-foreground: #f8f8f2; + --vscode-editorSuggestWidget-selectedForeground: #f8f8f2; + --vscode-editorSuggestWidget-selectedBackground: #44475a; + --vscode-editorSuggestWidget-highlightForeground: #8be9fd; + --vscode-editorSuggestWidget-focusHighlightForeground: #8be9fd; + --vscode-editorSuggestWidgetStatus-foreground: rgba(248, 248, 242, 0.5); + --vscode-tab-activeBackground: #282a36; + --vscode-tab-unfocusedActiveBackground: #282a36; + --vscode-tab-inactiveBackground: #21222c; + --vscode-tab-unfocusedInactiveBackground: #21222c; + --vscode-tab-activeForeground: #f8f8f2; + --vscode-tab-inactiveForeground: #6272a4; + --vscode-tab-unfocusedActiveForeground: rgba(248, 248, 242, 0.5); + --vscode-tab-unfocusedInactiveForeground: rgba(98, 114, 164, 0.5); + --vscode-tab-border: #191a21; + --vscode-tab-lastPinnedBorder: #585858; + --vscode-tab-activeBorderTop: rgba(255, 121, 198, 0.5); + --vscode-tab-unfocusedActiveBorderTop: rgba(255, 121, 198, 0.25); + --vscode-tab-activeModifiedBorder: #3399cc; + --vscode-tab-inactiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 153, 204, 0.25); + --vscode-editorPane-background: #282a36; + --vscode-editorGroupHeader-tabsBackground: #191a21; + --vscode-editorGroupHeader-noTabsBackground: #282a36; + --vscode-editorGroup-border: #bd93f9; + --vscode-editorGroup-dropBackground: rgba(68, 71, 90, 0.44); + --vscode-editorGroup-dropIntoPromptForeground: #f8f8f2; + --vscode-editorGroup-dropIntoPromptBackground: #21222c; + --vscode-sideBySideEditor-horizontalBorder: #bd93f9; + --vscode-sideBySideEditor-verticalBorder: #bd93f9; + --vscode-panel-background: #282a36; + --vscode-panel-border: #bd93f9; + --vscode-panelTitle-activeForeground: #f8f8f2; + --vscode-panelTitle-inactiveForeground: #6272a4; + --vscode-panelTitle-activeBorder: #ff79c6; + --vscode-panelInput-border: #191a21; + --vscode-panel-dropBorder: #f8f8f2; + --vscode-panelSection-dropBackground: rgba(68, 71, 90, 0.44); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: #bd93f9; + --vscode-banner-background: #44475a; + --vscode-banner-foreground: #f8f8f2; + --vscode-banner-iconForeground: #3794ff; + --vscode-statusBar-foreground: #f8f8f2; + --vscode-statusBar-noFolderForeground: #f8f8f2; + --vscode-statusBar-background: #191a21; + --vscode-statusBar-noFolderBackground: #191a21; + --vscode-statusBar-focusBorder: #f8f8f2; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #f8f8f2; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #f8f8f2; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #f8f8f2; + --vscode-statusBarItem-prominentBackground: #ff5555; + --vscode-statusBarItem-prominentHoverForeground: #f8f8f2; + --vscode-statusBarItem-prominentHoverBackground: #ffb86c; + --vscode-statusBarItem-errorBackground: #cc0000; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #f8f8f2; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #04bde7; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #f8f8f2; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #343746; + --vscode-activityBar-foreground: #f8f8f2; + --vscode-activityBar-inactiveForeground: #6272a4; + --vscode-activityBar-activeBorder: rgba(255, 121, 198, 0.5); + --vscode-activityBar-activeBackground: rgba(189, 147, 249, 0.06); + --vscode-activityBar-dropBorder: #f8f8f2; + --vscode-activityBarBadge-background: #ff79c6; + --vscode-activityBarBadge-foreground: #f8f8f2; + --vscode-profileBadge-background: #4d4d4d; + --vscode-profileBadge-foreground: #ffffff; + --vscode-statusBarItem-remoteBackground: #bd93f9; + --vscode-statusBarItem-remoteForeground: #282a36; + --vscode-statusBarItem-remoteHoverForeground: #f8f8f2; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #282a36; + --vscode-statusBarItem-offlineHoverForeground: #f8f8f2; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #ff79c6; + --vscode-extensionBadge-remoteForeground: #f8f8f2; + --vscode-sideBar-background: #21222c; + --vscode-sideBarTitle-foreground: #f8f8f2; + --vscode-sideBar-dropBackground: rgba(68, 71, 90, 0.44); + --vscode-sideBarSectionHeader-background: #282a36; + --vscode-sideBarSectionHeader-border: #191a21; + --vscode-titleBar-activeForeground: #f8f8f2; + --vscode-titleBar-inactiveForeground: #6272a4; + --vscode-titleBar-activeBackground: #21222c; + --vscode-titleBar-inactiveBackground: #191a21; + --vscode-menubar-selectionForeground: #f8f8f2; + --vscode-menubar-selectionBackground: rgba(90, 93, 94, 0.31); + --vscode-notifications-foreground: #f8f8f2; + --vscode-notifications-background: #21222c; + --vscode-notificationLink-foreground: #3794ff; + --vscode-notificationCenterHeader-background: #2b2c39; + --vscode-notifications-border: #2b2c39; + --vscode-notificationsErrorIcon-foreground: #ff5555; + --vscode-notificationsWarningIcon-foreground: #8be9fd; + --vscode-notificationsInfoIcon-foreground: #3794ff; + --vscode-commandCenter-foreground: #f8f8f2; + --vscode-commandCenter-activeForeground: #f8f8f2; + --vscode-commandCenter-inactiveForeground: #6272a4; + --vscode-commandCenter-background: rgba(255, 255, 255, 0.05); + --vscode-commandCenter-activeBackground: rgba(255, 255, 255, 0.08); + --vscode-commandCenter-border: rgba(248, 248, 242, 0.2); + --vscode-commandCenter-activeBorder: rgba(248, 248, 242, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(98, 114, 164, 0.25); + --vscode-chat-requestBorder: rgba(255, 255, 255, 0.1); + --vscode-chat-slashCommandBackground: #44475a; + --vscode-chat-slashCommandForeground: #f8f8f2; + --vscode-simpleFindWidget-sashBorder: #454545; + --vscode-commentsView-resolvedIcon: rgba(204, 204, 204, 0.5); + --vscode-commentsView-unresolvedIcon: #6272a4; + --vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #6272a4; + --vscode-editorCommentsWidget-rangeBackground: rgba(98, 114, 164, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(98, 114, 164, 0.1); + --vscode-editorGutter-commentRangeForeground: #343746; + --vscode-editorOverviewRuler-commentForeground: #343746; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #343746; + --vscode-editorGutter-commentGlyphForeground: #f8f8f2; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #f8f8f2; + --vscode-debugToolBar-background: #21222c; + --vscode-debugIcon-startForeground: #89d185; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.2); + --vscode-mergeEditor-changeBase\.background: #4b1818; + --vscode-mergeEditor-changeBase\.word\.background: #6f1313; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, 0.48); + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(80, 250, 123, 0.23); + --vscode-mergeEditor-conflict\.input2\.background: rgba(189, 147, 249, 0.23); + --vscode-settings-headerForeground: #f8f8f2; + --vscode-settings-settingsHeaderHoverForeground: rgba(248, 248, 242, 0.7); + --vscode-settings-modifiedItemIndicator: #ffb86c; + --vscode-settings-headerBorder: #bd93f9; + --vscode-settings-sashBorder: #bd93f9; + --vscode-settings-dropdownBackground: #21222c; + --vscode-settings-dropdownForeground: #f8f8f2; + --vscode-settings-dropdownBorder: #191a21; + --vscode-settings-dropdownListBorder: #454545; + --vscode-settings-checkboxBackground: #21222c; + --vscode-settings-checkboxForeground: #f8f8f2; + --vscode-settings-checkboxBorder: #191a21; + --vscode-settings-textInputBackground: #21222c; + --vscode-settings-textInputForeground: #f8f8f2; + --vscode-settings-textInputBorder: #191a21; + --vscode-settings-numberInputBackground: #21222c; + --vscode-settings-numberInputForeground: #f8f8f2; + --vscode-settings-numberInputBorder: #191a21; + --vscode-settings-focusedRowBackground: rgba(68, 71, 90, 0.28); + --vscode-settings-rowHoverBackground: rgba(68, 71, 90, 0.14); + --vscode-settings-focusedRowBorder: #6272a4; + --vscode-terminal-background: #282a36; + --vscode-terminal-foreground: #f8f8f2; + --vscode-terminal-selectionBackground: #44475a; + --vscode-terminal-inactiveSelectionBackground: rgba(68, 71, 90, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, 0.25); + --vscode-terminalCommandDecoration-successBackground: #1b81a8; + --vscode-terminalCommandDecoration-errorBackground: #f14c4c; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: #bd93f9; + --vscode-terminal-findMatchBackground: rgba(255, 184, 108, 0.5); + --vscode-terminal-hoverHighlightBackground: rgba(139, 233, 253, 0.16); + --vscode-terminal-findMatchHighlightBackground: rgba(255, 255, 255, 0.25); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(68, 71, 90, 0.44); + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #ff5555; + --vscode-testing-peekHeaderBackground: rgba(255, 85, 85, 0.1); + --vscode-testing-message\.error\.decorationForeground: #ff5555; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(248, 248, 242, 0.5); + --vscode-welcomePage-tileBackground: #21222c; + --vscode-welcomePage-tileHoverBackground: #282935; + --vscode-welcomePage-tileBorder: rgba(255, 255, 255, 0.1); + --vscode-welcomePage-progress\.background: #282a36; + --vscode-welcomePage-progress\.foreground: #3794ff; + --vscode-walkthrough-stepTitle\.foreground: #ffffff; + --vscode-walkThrough-embeddedEditorBackground: #21222c; + --vscode-inlineChat-background: #21222c; + --vscode-inlineChat-border: #454545; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.36); + --vscode-inlineChat-regionHighlight: #343746; + --vscode-inlineChatInput-border: #454545; + --vscode-inlineChatInput-focusBorder: #6272a4; + --vscode-inlineChatInput-placeholderForeground: #6272a4; + --vscode-inlineChatInput-background: #282a36; + --vscode-inlineChatDiff-inserted: rgba(80, 250, 123, 0.06); + --vscode-inlineChatDiff-removed: rgba(255, 85, 85, 0.16); + --vscode-debugExceptionWidget-border: #a31515; + --vscode-debugExceptionWidget-background: #420b0d; + --vscode-ports-iconRunningProcessForeground: #bd93f9; + --vscode-statusBar-debuggingBackground: #ff5555; + --vscode-statusBar-debuggingForeground: #191a21; + --vscode-editor-inlineValuesForeground: rgba(255, 255, 255, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: rgba(139, 233, 253, 0.5); + --vscode-editorGutter-addedBackground: rgba(80, 250, 123, 0.5); + --vscode-editorGutter-deletedBackground: rgba(255, 85, 85, 0.5); + --vscode-minimapGutter-modifiedBackground: rgba(139, 233, 253, 0.5); + --vscode-minimapGutter-addedBackground: rgba(80, 250, 123, 0.5); + --vscode-minimapGutter-deletedBackground: rgba(255, 85, 85, 0.5); + --vscode-editorOverviewRuler-modifiedForeground: rgba(139, 233, 253, 0.5); + --vscode-editorOverviewRuler-addedForeground: rgba(80, 250, 123, 0.5); + --vscode-editorOverviewRuler-deletedForeground: rgba(255, 85, 85, 0.5); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: rgba(68, 71, 90, 0.46); + --vscode-notebook-focusedEditorBorder: #6272a4; + --vscode-notebookStatusSuccessIcon-foreground: #89d185; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #89d185; + --vscode-notebookStatusErrorIcon-foreground: #ff5555; + --vscode-notebookStatusRunningIcon-foreground: #f8f8f2; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: rgba(68, 71, 90, 0.46); + --vscode-notebook-selectedCellBorder: rgba(68, 71, 90, 0.46); + --vscode-notebook-focusedCellBorder: #6272a4; + --vscode-notebook-inactiveFocusedCellBorder: rgba(68, 71, 90, 0.46); + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, 0.15); + --vscode-notebook-cellInsertionIndicator: #6272a4; + --vscode-notebookScrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-notebook-cellEditorBackground: #21222c; + --vscode-notebook-editorBackground: #282a36; + --vscode-keybindingTable-headerBackground: rgba(248, 248, 242, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(248, 248, 242, 0.04); + --vscode-searchEditor-textInputBorder: #191a21; + --vscode-debugTokenExpression-name: #c586c0; + --vscode-debugTokenExpression-value: rgba(204, 204, 204, 0.6); + --vscode-debugTokenExpression-string: #ce9178; + --vscode-debugTokenExpression-boolean: #4e94ce; + --vscode-debugTokenExpression-number: #b5cea8; + --vscode-debugTokenExpression-error: #f48771; + --vscode-debugView-exceptionLabelForeground: #f8f8f2; + --vscode-debugView-exceptionLabelBackground: #6c2022; + --vscode-debugView-stateLabelForeground: #f8f8f2; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #3794ff; + --vscode-debugConsole-warningForeground: #8be9fd; + --vscode-debugConsole-errorForeground: #ff5555; + --vscode-debugConsole-sourceForeground: #f8f8f2; + --vscode-debugConsoleInputIcon-foreground: #f8f8f2; + --vscode-debugIcon-pauseForeground: #75beff; + --vscode-debugIcon-stopForeground: #f48771; + --vscode-debugIcon-disconnectForeground: #f48771; + --vscode-debugIcon-restartForeground: #89d185; + --vscode-debugIcon-stepOverForeground: #75beff; + --vscode-debugIcon-stepIntoForeground: #75beff; + --vscode-debugIcon-stepOutForeground: #75beff; + --vscode-debugIcon-continueForeground: #75beff; + --vscode-debugIcon-stepBackForeground: #75beff; + --vscode-scm-providerBorder: #454545; + --vscode-extensionButton-background: #44475a; + --vscode-extensionButton-foreground: #f8f8f2; + --vscode-extensionButton-hoverBackground: #52556c; + --vscode-extensionButton-separator: rgba(248, 248, 242, 0.4); + --vscode-extensionButton-prominentBackground: rgba(80, 250, 123, 0.56); + --vscode-extensionButton-prominentForeground: #f8f8f2; + --vscode-extensionButton-prominentHoverBackground: rgba(80, 250, 123, 0.38); + --vscode-extensionIcon-starForeground: #ff8e00; + --vscode-extensionIcon-verifiedForeground: #3794ff; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #d758b3; + --vscode-terminal-ansiBlack: #21222c; + --vscode-terminal-ansiRed: #ff5555; + --vscode-terminal-ansiGreen: #50fa7b; + --vscode-terminal-ansiYellow: #f1fa8c; + --vscode-terminal-ansiBlue: #bd93f9; + --vscode-terminal-ansiMagenta: #ff79c6; + --vscode-terminal-ansiCyan: #8be9fd; + --vscode-terminal-ansiWhite: #f8f8f2; + --vscode-terminal-ansiBrightBlack: #6272a4; + --vscode-terminal-ansiBrightRed: #ff6e6e; + --vscode-terminal-ansiBrightGreen: #69ff94; + --vscode-terminal-ansiBrightYellow: #ffffa5; + --vscode-terminal-ansiBrightBlue: #d6acff; + --vscode-terminal-ansiBrightMagenta: #ff92df; + --vscode-terminal-ansiBrightCyan: #a4ffff; + --vscode-terminal-ansiBrightWhite: #ffffff; + --vscode-interactive-activeCodeBorder: #44475a; + --vscode-interactive-inactiveCodeBorder: rgba(68, 71, 90, 0.46); + --vscode-gitDecoration-addedResourceForeground: #81b88b; + --vscode-gitDecoration-modifiedResourceForeground: #8be9fd; + --vscode-gitDecoration-deletedResourceForeground: #ff5555; + --vscode-gitDecoration-renamedResourceForeground: #73c991; + --vscode-gitDecoration-untrackedResourceForeground: #50fa7b; + --vscode-gitDecoration-ignoredResourceForeground: #6272a4; + --vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-stageDeletedResourceForeground: #c74e39; + --vscode-gitDecoration-conflictingResourceForeground: #ffb86c; + --vscode-gitDecoration-submoduleResourceForeground: #8db9e2; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/dark-plus.scss b/vendor/mynah-ui/example/src/styles/themes/dark-plus.scss new file mode 100644 index 00000000..b6204267 --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/dark-plus.scss @@ -0,0 +1,611 @@ +html[theme='dark-plus']:root { + --vscode-foreground: #cccccc; + --vscode-disabledForeground: rgba(204, 204, 204, 0.5); + --vscode-errorForeground: #f48771; + --vscode-descriptionForeground: rgba(204, 204, 204, 0.7); + --vscode-icon-foreground: #c5c5c5; + --vscode-focusBorder: #007fd4; + --vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18); + --vscode-textLink-foreground: #3794ff; + --vscode-textLink-activeForeground: #3794ff; + --vscode-textPreformat-foreground: #d7ba7d; + --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1); + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4); + --vscode-widget-shadow: rgba(0, 0, 0, 0.36); + --vscode-widget-border: #303031; + --vscode-input-background: #3c3c3c; + --vscode-input-foreground: #cccccc; + --vscode-inputOption-activeBorder: #007acc; + --vscode-inputOption-hoverBackground: rgba(90, 93, 94, 0.5); + --vscode-inputOption-activeBackground: rgba(0, 127, 212, 0.4); + --vscode-inputOption-activeForeground: #ffffff; + --vscode-input-placeholderForeground: #a6a6a6; + --vscode-inputValidation-infoBackground: #063b49; + --vscode-inputValidation-infoBorder: #007acc; + --vscode-inputValidation-warningBackground: #352a05; + --vscode-inputValidation-warningBorder: #b89500; + --vscode-inputValidation-errorBackground: #5a1d1d; + --vscode-inputValidation-errorBorder: #be1100; + --vscode-dropdown-background: #3c3c3c; + --vscode-dropdown-foreground: #f0f0f0; + --vscode-dropdown-border: #3c3c3c; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #0e639c; + --vscode-button-hoverBackground: #1177bb; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #3a3d41; + --vscode-button-secondaryHoverBackground: #45494e; + --vscode-badge-background: #4d4d4d; + --vscode-badge-foreground: #ffffff; + --vscode-scrollbar-shadow: #000000; + --vscode-scrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-progressBar-background: #0e70c0; + --vscode-editorError-foreground: #f14c4c; + --vscode-editorWarning-foreground: #cca700; + --vscode-editorInfo-foreground: #3794ff; + --vscode-editorHint-foreground: rgba(238, 238, 238, 0.7); + --vscode-sash-hoverBorder: #007fd4; + --vscode-editor-background: #1e1e1e; + --vscode-editor-foreground: #d4d4d4; + --vscode-editorStickyScroll-background: #1e1e1e; + --vscode-editorStickyScrollHover-background: #2a2d2e; + --vscode-editorWidget-background: #252526; + --vscode-editorWidget-foreground: #cccccc; + --vscode-editorWidget-border: #454545; + --vscode-quickInput-background: #252526; + --vscode-quickInput-foreground: #cccccc; + --vscode-quickInputTitle-background: rgba(255, 255, 255, 0.1); + --vscode-pickerGroup-foreground: #3794ff; + --vscode-pickerGroup-border: #3f3f46; + --vscode-keybindingLabel-background: rgba(128, 128, 128, 0.17); + --vscode-keybindingLabel-foreground: #cccccc; + --vscode-keybindingLabel-border: rgba(51, 51, 51, 0.6); + --vscode-keybindingLabel-bottomBorder: rgba(68, 68, 68, 0.6); + --vscode-editor-selectionBackground: #264f78; + --vscode-editor-inactiveSelectionBackground: #3a3d41; + --vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 0.15); + --vscode-editor-findMatchBackground: #515c6a; + --vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4); + --vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 0.22); + --vscode-search-resultsInfoForeground: rgba(204, 204, 204, 0.65); + --vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25); + --vscode-editorHoverWidget-background: #252526; + --vscode-editorHoverWidget-foreground: #cccccc; + --vscode-editorHoverWidget-border: #454545; + --vscode-editorHoverWidget-statusBarBackground: #2c2c2d; + --vscode-editorLink-activeForeground: #4e94ce; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(77, 77, 77, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(77, 77, 77, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(77, 77, 77, 0.1); + --vscode-editorLightBulb-foreground: #ffcc00; + --vscode-editorLightBulbAutoFix-foreground: #75beff; + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.2); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(204, 204, 204, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #3e3e3e; + --vscode-diffEditor-unchangedRegionForeground: #a3a2a2; + --vscode-diffEditor-unchangedCodeBackground: rgba(116, 116, 116, 0.16); + --vscode-list-focusOutline: #007fd4; + --vscode-list-activeSelectionBackground: #04395e; + --vscode-list-activeSelectionForeground: #ffffff; + --vscode-list-activeSelectionIconForeground: #ffffff; + --vscode-list-inactiveSelectionBackground: #37373d; + --vscode-list-hoverBackground: #2a2d2e; + --vscode-list-dropBackground: #383b3d; + --vscode-list-highlightForeground: #2aaaff; + --vscode-list-focusHighlightForeground: #2aaaff; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #f88070; + --vscode-list-warningForeground: #cca700; + --vscode-listFilterWidget-background: #252526; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.36); + --vscode-list-filterMatchBackground: rgba(234, 92, 0, 0.33); + --vscode-tree-indentGuidesStroke: #585858; + --vscode-tree-inactiveIndentGuidesStroke: rgba(88, 88, 88, 0.4); + --vscode-tree-tableColumnsBorder: rgba(204, 204, 204, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(204, 204, 204, 0.04); + --vscode-list-deemphasizedForeground: #8c8c8c; + --vscode-checkbox-background: #3c3c3c; + --vscode-checkbox-selectBackground: #252526; + --vscode-checkbox-foreground: #f0f0f0; + --vscode-checkbox-border: #6b6b6b; + --vscode-checkbox-selectBorder: #c5c5c5; + --vscode-quickInputList-focusForeground: #ffffff; + --vscode-quickInputList-focusIconForeground: #ffffff; + --vscode-quickInputList-focusBackground: #04395e; + --vscode-menu-border: #454545; + --vscode-menu-foreground: #cccccc; + --vscode-menu-background: #252526; + --vscode-menu-selectionForeground: #ffffff; + --vscode-menu-selectionBackground: #04395e; + --vscode-menu-separatorBackground: #454545; + --vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31); + --vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, 0.3); + --vscode-editor-snippetFinalTabstopHighlightBorder: #525252; + --vscode-breadcrumb-foreground: rgba(204, 204, 204, 0.8); + --vscode-breadcrumb-background: #1e1e1e; + --vscode-breadcrumb-focusForeground: #e0e0e0; + --vscode-breadcrumb-activeSelectionForeground: #e0e0e0; + --vscode-breadcrumbPicker-background: #252526; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #676767; + --vscode-minimap-selectionHighlight: #264f78; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #cca700; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(121, 121, 121, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(191, 191, 191, 0.2); + --vscode-problemsErrorIcon-foreground: #f14c4c; + --vscode-problemsWarningIcon-foreground: #cca700; + --vscode-problemsInfoIcon-foreground: #3794ff; + --vscode-charts-foreground: #cccccc; + --vscode-charts-lines: rgba(204, 204, 204, 0.5); + --vscode-charts-red: #f14c4c; + --vscode-charts-blue: #3794ff; + --vscode-charts-yellow: #cca700; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #89d185; + --vscode-charts-purple: #b180d7; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #cccccc; + --vscode-symbolIcon-booleanForeground: #cccccc; + --vscode-symbolIcon-classForeground: #ee9d28; + --vscode-symbolIcon-colorForeground: #cccccc; + --vscode-symbolIcon-constantForeground: #cccccc; + --vscode-symbolIcon-constructorForeground: #b180d7; + --vscode-symbolIcon-enumeratorForeground: #ee9d28; + --vscode-symbolIcon-enumeratorMemberForeground: #75beff; + --vscode-symbolIcon-eventForeground: #ee9d28; + --vscode-symbolIcon-fieldForeground: #75beff; + --vscode-symbolIcon-fileForeground: #cccccc; + --vscode-symbolIcon-folderForeground: #cccccc; + --vscode-symbolIcon-functionForeground: #b180d7; + --vscode-symbolIcon-interfaceForeground: #75beff; + --vscode-symbolIcon-keyForeground: #cccccc; + --vscode-symbolIcon-keywordForeground: #cccccc; + --vscode-symbolIcon-methodForeground: #b180d7; + --vscode-symbolIcon-moduleForeground: #cccccc; + --vscode-symbolIcon-namespaceForeground: #cccccc; + --vscode-symbolIcon-nullForeground: #cccccc; + --vscode-symbolIcon-numberForeground: #cccccc; + --vscode-symbolIcon-objectForeground: #cccccc; + --vscode-symbolIcon-operatorForeground: #cccccc; + --vscode-symbolIcon-packageForeground: #cccccc; + --vscode-symbolIcon-propertyForeground: #cccccc; + --vscode-symbolIcon-referenceForeground: #cccccc; + --vscode-symbolIcon-snippetForeground: #cccccc; + --vscode-symbolIcon-stringForeground: #cccccc; + --vscode-symbolIcon-structForeground: #cccccc; + --vscode-symbolIcon-textForeground: #cccccc; + --vscode-symbolIcon-typeParameterForeground: #cccccc; + --vscode-symbolIcon-unitForeground: #cccccc; + --vscode-symbolIcon-variableForeground: #75beff; + --vscode-actionBar-toggledBackground: #383a49; + --vscode-editorHoverWidget-highlightForeground: #2aaaff; + --vscode-editor-lineHighlightBorder: #282828; + --vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editorCursor-foreground: #aeafad; + --vscode-editorWhitespace-foreground: rgba(227, 228, 226, 0.16); + --vscode-editorLineNumber-foreground: #858585; + --vscode-editorIndentGuide-background: #404040; + --vscode-editorIndentGuide-activeBackground: #707070; + --vscode-editorIndentGuide-background1: #404040; + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: #707070; + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #c6c6c6; + --vscode-editorLineNumber-activeForeground: #c6c6c6; + --vscode-editorRuler-foreground: #5a5a5a; + --vscode-editorCodeLens-foreground: #999999; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #888888; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #1e1e1e; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67); + --vscode-editorGhostText-foreground: rgba(255, 255, 255, 0.34); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #cca700; + --vscode-editorOverviewRuler-infoForeground: #3794ff; + --vscode-editorBracketHighlight-foreground1: #ffd700; + --vscode-editorBracketHighlight-foreground2: #da70d6; + --vscode-editorBracketHighlight-foreground3: #179fff; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #bd9b03; + --vscode-editorUnicodeHighlight-background: rgba(189, 155, 3, 0.15); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(38, 79, 120, 0.3); + --vscode-editorGutter-foldingControlForeground: #c5c5c5; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.72); + --vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, 0.72); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.72); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: #252526; + --vscode-peekViewTitleLabel-foreground: #ffffff; + --vscode-peekViewTitleDescription-foreground: rgba(204, 204, 204, 0.7); + --vscode-peekView-border: #3794ff; + --vscode-peekViewResult-background: #252526; + --vscode-peekViewResult-lineForeground: #bbbbbb; + --vscode-peekViewResult-fileForeground: #ffffff; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #ffffff; + --vscode-peekViewEditor-background: #001f33; + --vscode-peekViewEditorGutter-background: #001f33; + --vscode-peekViewEditorStickyScroll-background: #001f33; + --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3); + --vscode-peekViewEditor-matchHighlightBackground: rgba(255, 143, 0, 0.6); + --vscode-editorMarkerNavigationError-background: #f14c4c; + --vscode-editorMarkerNavigationError-headerBackground: rgba(241, 76, 76, 0.1); + --vscode-editorMarkerNavigationWarning-background: #cca700; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(204, 167, 0, 0.1); + --vscode-editorMarkerNavigationInfo-background: #3794ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, 0.1); + --vscode-editorMarkerNavigation-background: #1e1e1e; + --vscode-editorSuggestWidget-background: #252526; + --vscode-editorSuggestWidget-border: #454545; + --vscode-editorSuggestWidget-foreground: #d4d4d4; + --vscode-editorSuggestWidget-selectedForeground: #ffffff; + --vscode-editorSuggestWidget-selectedIconForeground: #ffffff; + --vscode-editorSuggestWidget-selectedBackground: #04395e; + --vscode-editorSuggestWidget-highlightForeground: #2aaaff; + --vscode-editorSuggestWidget-focusHighlightForeground: #2aaaff; + --vscode-editorSuggestWidgetStatus-foreground: rgba(212, 212, 212, 0.5); + --vscode-tab-activeBackground: #1e1e1e; + --vscode-tab-unfocusedActiveBackground: #1e1e1e; + --vscode-tab-inactiveBackground: #2d2d2d; + --vscode-tab-unfocusedInactiveBackground: #2d2d2d; + --vscode-tab-activeForeground: #ffffff; + --vscode-tab-inactiveForeground: rgba(255, 255, 255, 0.5); + --vscode-tab-unfocusedActiveForeground: rgba(255, 255, 255, 0.5); + --vscode-tab-unfocusedInactiveForeground: rgba(255, 255, 255, 0.25); + --vscode-tab-border: #252526; + --vscode-tab-lastPinnedBorder: rgba(204, 204, 204, 0.2); + --vscode-tab-activeModifiedBorder: #3399cc; + --vscode-tab-inactiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 153, 204, 0.25); + --vscode-editorPane-background: #1e1e1e; + --vscode-editorGroupHeader-tabsBackground: #252526; + --vscode-editorGroupHeader-noTabsBackground: #1e1e1e; + --vscode-editorGroup-border: #444444; + --vscode-editorGroup-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-editorGroup-dropIntoPromptForeground: #cccccc; + --vscode-editorGroup-dropIntoPromptBackground: #252526; + --vscode-sideBySideEditor-horizontalBorder: #444444; + --vscode-sideBySideEditor-verticalBorder: #444444; + --vscode-panel-background: #1e1e1e; + --vscode-panel-border: rgba(128, 128, 128, 0.35); + --vscode-panelTitle-activeForeground: #e7e7e7; + --vscode-panelTitle-inactiveForeground: rgba(231, 231, 231, 0.6); + --vscode-panelTitle-activeBorder: #e7e7e7; + --vscode-panel-dropBorder: #e7e7e7; + --vscode-panelSection-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: rgba(128, 128, 128, 0.35); + --vscode-banner-background: #04395e; + --vscode-banner-foreground: #ffffff; + --vscode-banner-iconForeground: #3794ff; + --vscode-statusBar-foreground: #ffffff; + --vscode-statusBar-noFolderForeground: #ffffff; + --vscode-statusBar-background: #007acc; + --vscode-statusBar-noFolderBackground: #68217a; + --vscode-statusBar-focusBorder: #ffffff; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #ffffff; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #ffffff; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #ffffff; + --vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5); + --vscode-statusBarItem-prominentHoverForeground: #ffffff; + --vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.3); + --vscode-statusBarItem-errorBackground: #c72e0f; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #ffffff; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #7a6400; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #ffffff; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #333333; + --vscode-activityBar-foreground: #ffffff; + --vscode-activityBar-inactiveForeground: rgba(255, 255, 255, 0.4); + --vscode-activityBar-activeBorder: #ffffff; + --vscode-activityBar-dropBorder: #ffffff; + --vscode-activityBarBadge-background: #007acc; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-profileBadge-background: #4d4d4d; + --vscode-profileBadge-foreground: #ffffff; + --vscode-statusBarItem-remoteBackground: #16825d; + --vscode-statusBarItem-remoteForeground: #ffffff; + --vscode-statusBarItem-remoteHoverForeground: #ffffff; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #ffffff; + --vscode-statusBarItem-offlineHoverForeground: #ffffff; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #007acc; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #252526; + --vscode-sideBarTitle-foreground: #bbbbbb; + --vscode-sideBar-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-sideBarSectionHeader-background: rgba(0, 0, 0, 0); + --vscode-sideBarSectionHeader-border: rgba(204, 204, 204, 0.2); + --vscode-titleBar-activeForeground: #cccccc; + --vscode-titleBar-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-titleBar-activeBackground: #3c3c3c; + --vscode-titleBar-inactiveBackground: rgba(60, 60, 60, 0.6); + --vscode-menubar-selectionForeground: #cccccc; + --vscode-menubar-selectionBackground: rgba(90, 93, 94, 0.31); + --vscode-notificationCenter-border: #303031; + --vscode-notificationToast-border: #303031; + --vscode-notifications-foreground: #cccccc; + --vscode-notifications-background: #252526; + --vscode-notificationLink-foreground: #3794ff; + --vscode-notificationCenterHeader-background: #303031; + --vscode-notifications-border: #303031; + --vscode-notificationsErrorIcon-foreground: #f14c4c; + --vscode-notificationsWarningIcon-foreground: #cca700; + --vscode-notificationsInfoIcon-foreground: #3794ff; + --vscode-commandCenter-foreground: #cccccc; + --vscode-commandCenter-activeForeground: #cccccc; + --vscode-commandCenter-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-commandCenter-background: rgba(255, 255, 255, 0.05); + --vscode-commandCenter-activeBackground: rgba(255, 255, 255, 0.08); + --vscode-commandCenter-border: rgba(204, 204, 204, 0.2); + --vscode-commandCenter-activeBorder: rgba(204, 204, 204, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(204, 204, 204, 0.15); + --vscode-chat-requestBorder: rgba(255, 255, 255, 0.1); + --vscode-chat-slashCommandBackground: #4d4d4d; + --vscode-chat-slashCommandForeground: #ffffff; + --vscode-simpleFindWidget-sashBorder: #454545; + --vscode-commentsView-resolvedIcon: rgba(204, 204, 204, 0.5); + --vscode-commentsView-unresolvedIcon: #007fd4; + --vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #007fd4; + --vscode-editorCommentsWidget-rangeBackground: rgba(0, 127, 212, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(0, 127, 212, 0.1); + --vscode-editorGutter-commentRangeForeground: #37373d; + --vscode-editorOverviewRuler-commentForeground: #37373d; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #37373d; + --vscode-editorGutter-commentGlyphForeground: #d4d4d4; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #d4d4d4; + --vscode-debugToolBar-background: #333333; + --vscode-debugIcon-startForeground: #89d185; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.2); + --vscode-mergeEditor-changeBase\.background: #4b1818; + --vscode-mergeEditor-changeBase\.word\.background: #6f1313; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, 0.48); + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #e7e7e7; + --vscode-settings-settingsHeaderHoverForeground: rgba(231, 231, 231, 0.7); + --vscode-settings-modifiedItemIndicator: #0c7d9d; + --vscode-settings-headerBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-sashBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-dropdownBackground: #3c3c3c; + --vscode-settings-dropdownForeground: #f0f0f0; + --vscode-settings-dropdownBorder: #3c3c3c; + --vscode-settings-dropdownListBorder: #454545; + --vscode-settings-checkboxBackground: #3c3c3c; + --vscode-settings-checkboxForeground: #f0f0f0; + --vscode-settings-checkboxBorder: #6b6b6b; + --vscode-settings-textInputBackground: #3c3c3c; + --vscode-settings-textInputForeground: #cccccc; + --vscode-settings-numberInputBackground: #3c3c3c; + --vscode-settings-numberInputForeground: #cccccc; + --vscode-settings-focusedRowBackground: rgba(42, 45, 46, 0.6); + --vscode-settings-rowHoverBackground: rgba(42, 45, 46, 0.3); + --vscode-settings-focusedRowBorder: #007fd4; + --vscode-terminal-foreground: #cccccc; + --vscode-terminal-selectionBackground: #264f78; + --vscode-terminal-inactiveSelectionBackground: #3a3d41; + --vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, 0.25); + --vscode-terminalCommandDecoration-successBackground: #1b81a8; + --vscode-terminalCommandDecoration-errorBackground: #f14c4c; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: rgba(128, 128, 128, 0.35); + --vscode-terminal-findMatchBackground: #515c6a; + --vscode-terminal-hoverHighlightBackground: rgba(38, 79, 120, 0.13); + --vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(83, 89, 93, 0.5); + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #f14c4c; + --vscode-testing-peekHeaderBackground: rgba(241, 76, 76, 0.1); + --vscode-testing-message\.error\.decorationForeground: #f14c4c; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(212, 212, 212, 0.5); + --vscode-welcomePage-tileBackground: #252526; + --vscode-welcomePage-tileHoverBackground: #2c2c2d; + --vscode-welcomePage-tileBorder: rgba(255, 255, 255, 0.1); + --vscode-welcomePage-progress\.background: #3c3c3c; + --vscode-welcomePage-progress\.foreground: #3794ff; + --vscode-walkthrough-stepTitle\.foreground: #ffffff; + --vscode-walkThrough-embeddedEditorBackground: rgba(0, 0, 0, 0.4); + --vscode-inlineChat-background: #252526; + --vscode-inlineChat-border: #454545; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.36); + --vscode-inlineChat-regionHighlight: rgba(38, 79, 120, 0.25); + --vscode-inlineChatInput-border: #454545; + --vscode-inlineChatInput-focusBorder: #007fd4; + --vscode-inlineChatInput-placeholderForeground: #a6a6a6; + --vscode-inlineChatInput-background: #3c3c3c; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.1); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-debugExceptionWidget-border: #a31515; + --vscode-debugExceptionWidget-background: #420b0d; + --vscode-ports-iconRunningProcessForeground: #369432; + --vscode-statusBar-debuggingBackground: #cc6633; + --vscode-statusBar-debuggingForeground: #ffffff; + --vscode-editor-inlineValuesForeground: rgba(255, 255, 255, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: #1b81a8; + --vscode-editorGutter-addedBackground: #487e02; + --vscode-editorGutter-deletedBackground: #f14c4c; + --vscode-minimapGutter-modifiedBackground: #1b81a8; + --vscode-minimapGutter-addedBackground: #487e02; + --vscode-minimapGutter-deletedBackground: #f14c4c; + --vscode-editorOverviewRuler-modifiedForeground: rgba(27, 129, 168, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 126, 2, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(241, 76, 76, 0.6); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: #37373d; + --vscode-notebook-focusedEditorBorder: #007fd4; + --vscode-notebookStatusSuccessIcon-foreground: #89d185; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #89d185; + --vscode-notebookStatusErrorIcon-foreground: #f48771; + --vscode-notebookStatusRunningIcon-foreground: #cccccc; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #37373d; + --vscode-notebook-selectedCellBorder: #37373d; + --vscode-notebook-focusedCellBorder: #007fd4; + --vscode-notebook-inactiveFocusedCellBorder: #37373d; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, 0.15); + --vscode-notebook-cellInsertionIndicator: #007fd4; + --vscode-notebookScrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-notebook-cellEditorBackground: #252526; + --vscode-notebook-editorBackground: #1e1e1e; + --vscode-keybindingTable-headerBackground: rgba(204, 204, 204, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(204, 204, 204, 0.04); + --vscode-debugTokenExpression-name: #c586c0; + --vscode-debugTokenExpression-value: rgba(204, 204, 204, 0.6); + --vscode-debugTokenExpression-string: #ce9178; + --vscode-debugTokenExpression-boolean: #4e94ce; + --vscode-debugTokenExpression-number: #b5cea8; + --vscode-debugTokenExpression-error: #f48771; + --vscode-debugView-exceptionLabelForeground: #cccccc; + --vscode-debugView-exceptionLabelBackground: #6c2022; + --vscode-debugView-stateLabelForeground: #cccccc; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #3794ff; + --vscode-debugConsole-warningForeground: #cca700; + --vscode-debugConsole-errorForeground: #f48771; + --vscode-debugConsole-sourceForeground: #cccccc; + --vscode-debugConsoleInputIcon-foreground: #cccccc; + --vscode-debugIcon-pauseForeground: #75beff; + --vscode-debugIcon-stopForeground: #f48771; + --vscode-debugIcon-disconnectForeground: #f48771; + --vscode-debugIcon-restartForeground: #89d185; + --vscode-debugIcon-stepOverForeground: #75beff; + --vscode-debugIcon-stepIntoForeground: #75beff; + --vscode-debugIcon-stepOutForeground: #75beff; + --vscode-debugIcon-continueForeground: #75beff; + --vscode-debugIcon-stepBackForeground: #75beff; + --vscode-scm-providerBorder: #454545; + --vscode-extensionButton-background: #0e639c; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #1177bb; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #0e639c; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: #1177bb; + --vscode-extensionIcon-starForeground: #ff8e00; + --vscode-extensionIcon-verifiedForeground: #3794ff; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #d758b3; + --vscode-terminal-ansiBlack: #000000; + --vscode-terminal-ansiRed: #cd3131; + --vscode-terminal-ansiGreen: #0dbc79; + --vscode-terminal-ansiYellow: #e5e510; + --vscode-terminal-ansiBlue: #2472c8; + --vscode-terminal-ansiMagenta: #bc3fbc; + --vscode-terminal-ansiCyan: #11a8cd; + --vscode-terminal-ansiWhite: #e5e5e5; + --vscode-terminal-ansiBrightBlack: #666666; + --vscode-terminal-ansiBrightRed: #f14c4c; + --vscode-terminal-ansiBrightGreen: #23d18b; + --vscode-terminal-ansiBrightYellow: #f5f543; + --vscode-terminal-ansiBrightBlue: #3b8eea; + --vscode-terminal-ansiBrightMagenta: #d670d6; + --vscode-terminal-ansiBrightCyan: #29b8db; + --vscode-terminal-ansiBrightWhite: #e5e5e5; + --vscode-interactive-activeCodeBorder: #3794ff; + --vscode-interactive-inactiveCodeBorder: #37373d; + --vscode-gitDecoration-addedResourceForeground: #81b88b; + --vscode-gitDecoration-modifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-deletedResourceForeground: #c74e39; + --vscode-gitDecoration-renamedResourceForeground: #73c991; + --vscode-gitDecoration-untrackedResourceForeground: #73c991; + --vscode-gitDecoration-ignoredResourceForeground: #8c8c8c; + --vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-stageDeletedResourceForeground: #c74e39; + --vscode-gitDecoration-conflictingResourceForeground: #e4676b; + --vscode-gitDecoration-submoduleResourceForeground: #8db9e2; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/dark-solarized.scss b/vendor/mynah-ui/example/src/styles/themes/dark-solarized.scss new file mode 100644 index 00000000..9f0cfb40 --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/dark-solarized.scss @@ -0,0 +1,605 @@ +html[theme='dark-solarized']:root { + --vscode-foreground: #cccccc; + --vscode-disabledForeground: rgba(204, 204, 204, 0.5); + --vscode-errorForeground: #ffeaea; + --vscode-descriptionForeground: rgba(204, 204, 204, 0.7); + --vscode-icon-foreground: #c5c5c5; + --vscode-focusBorder: rgba(42, 161, 152, 0.6); + --vscode-selection-background: rgba(42, 161, 152, 0.6); + --vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18); + --vscode-textLink-foreground: #3794ff; + --vscode-textLink-activeForeground: #3794ff; + --vscode-textPreformat-foreground: #d7ba7d; + --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1); + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4); + --vscode-widget-shadow: rgba(0, 0, 0, 0.36); + --vscode-input-background: #003847; + --vscode-input-foreground: #93a1a1; + --vscode-inputOption-activeBorder: rgba(42, 161, 152, 0.6); + --vscode-inputOption-hoverBackground: rgba(90, 93, 94, 0.5); + --vscode-inputOption-activeBackground: rgba(42, 161, 152, 0.24); + --vscode-inputOption-activeForeground: #ffffff; + --vscode-input-placeholderForeground: rgba(147, 161, 161, 0.67); + --vscode-inputValidation-infoBackground: #052730; + --vscode-inputValidation-infoBorder: #363b5f; + --vscode-inputValidation-warningBackground: #5d5938; + --vscode-inputValidation-warningBorder: #9d8a5e; + --vscode-inputValidation-errorBackground: #571b26; + --vscode-inputValidation-errorBorder: #a92049; + --vscode-dropdown-background: #00212b; + --vscode-dropdown-foreground: #f0f0f0; + --vscode-dropdown-border: rgba(42, 161, 152, 0.6); + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: rgba(42, 161, 152, 0.6); + --vscode-button-hoverBackground: rgba(50, 193, 181, 0.6); + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #3a3d41; + --vscode-button-secondaryHoverBackground: #45494e; + --vscode-badge-background: #047aa6; + --vscode-badge-foreground: #ffffff; + --vscode-scrollbar-shadow: #000000; + --vscode-scrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-progressBar-background: #047aa6; + --vscode-editorError-foreground: #f14c4c; + --vscode-editorWarning-foreground: #cca700; + --vscode-editorInfo-foreground: #3794ff; + --vscode-editorHint-foreground: rgba(238, 238, 238, 0.7); + --vscode-sash-hoverBorder: rgba(42, 161, 152, 0.6); + --vscode-editor-background: #002b36; + --vscode-editor-foreground: #839496; + --vscode-editorStickyScroll-background: #002b36; + --vscode-editorStickyScrollHover-background: #2a2d2e; + --vscode-editorWidget-background: #00212b; + --vscode-editorWidget-foreground: #cccccc; + --vscode-editorWidget-border: #454545; + --vscode-quickInput-background: #00212b; + --vscode-quickInput-foreground: #cccccc; + --vscode-quickInputTitle-background: rgba(255, 255, 255, 0.1); + --vscode-pickerGroup-foreground: rgba(42, 161, 152, 0.6); + --vscode-pickerGroup-border: rgba(42, 161, 152, 0.6); + --vscode-keybindingLabel-background: rgba(128, 128, 128, 0.17); + --vscode-keybindingLabel-foreground: #cccccc; + --vscode-keybindingLabel-border: rgba(51, 51, 51, 0.6); + --vscode-keybindingLabel-bottomBorder: rgba(68, 68, 68, 0.6); + --vscode-editor-selectionBackground: #274642; + --vscode-editor-inactiveSelectionBackground: rgba(39, 70, 66, 0.5); + --vscode-editor-selectionHighlightBackground: rgba(0, 90, 111, 0.67); + --vscode-editor-findMatchBackground: #515c6a; + --vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4); + --vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 0.22); + --vscode-search-resultsInfoForeground: rgba(204, 204, 204, 0.65); + --vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25); + --vscode-editorHoverWidget-background: #004052; + --vscode-editorHoverWidget-foreground: #cccccc; + --vscode-editorHoverWidget-border: #454545; + --vscode-editorHoverWidget-statusBarBackground: #004d62; + --vscode-editorLink-activeForeground: #4e94ce; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(4, 122, 166, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(4, 122, 166, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(4, 122, 166, 0.1); + --vscode-editorLightBulb-foreground: #ffcc00; + --vscode-editorLightBulbAutoFix-foreground: #75beff; + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.2); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(204, 204, 204, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #3e3e3e; + --vscode-diffEditor-unchangedRegionForeground: #a3a2a2; + --vscode-diffEditor-unchangedCodeBackground: rgba(116, 116, 116, 0.16); + --vscode-list-focusOutline: rgba(42, 161, 152, 0.6); + --vscode-list-activeSelectionBackground: #005a6f; + --vscode-list-activeSelectionForeground: #ffffff; + --vscode-list-inactiveSelectionBackground: rgba(0, 68, 84, 0.53); + --vscode-list-hoverBackground: rgba(0, 68, 84, 0.67); + --vscode-list-dropBackground: rgba(0, 68, 84, 0.53); + --vscode-list-highlightForeground: #1ebcc5; + --vscode-list-focusHighlightForeground: #1ebcc5; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #f88070; + --vscode-list-warningForeground: #cca700; + --vscode-listFilterWidget-background: #00212b; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.36); + --vscode-list-filterMatchBackground: rgba(234, 92, 0, 0.33); + --vscode-tree-indentGuidesStroke: #585858; + --vscode-tree-inactiveIndentGuidesStroke: rgba(88, 88, 88, 0.4); + --vscode-tree-tableColumnsBorder: rgba(204, 204, 204, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(204, 204, 204, 0.04); + --vscode-list-deemphasizedForeground: #8c8c8c; + --vscode-checkbox-background: #00212b; + --vscode-checkbox-selectBackground: #00212b; + --vscode-checkbox-foreground: #f0f0f0; + --vscode-checkbox-border: rgba(42, 161, 152, 0.6); + --vscode-checkbox-selectBorder: #c5c5c5; + --vscode-quickInputList-focusForeground: #ffffff; + --vscode-quickInputList-focusBackground: #005a6f; + --vscode-menu-foreground: #f0f0f0; + --vscode-menu-background: #00212b; + --vscode-menu-selectionForeground: #ffffff; + --vscode-menu-selectionBackground: #005a6f; + --vscode-menu-separatorBackground: #606060; + --vscode-toolbar-hoverBackground: rgba(90, 93, 94, 0.31); + --vscode-toolbar-activeBackground: rgba(99, 102, 103, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, 0.3); + --vscode-editor-snippetFinalTabstopHighlightBorder: #525252; + --vscode-breadcrumb-foreground: rgba(204, 204, 204, 0.8); + --vscode-breadcrumb-background: #002b36; + --vscode-breadcrumb-focusForeground: #e0e0e0; + --vscode-breadcrumb-activeSelectionForeground: #e0e0e0; + --vscode-breadcrumbPicker-background: #00212b; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #676767; + --vscode-minimap-selectionHighlight: #274642; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #cca700; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(121, 121, 121, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(191, 191, 191, 0.2); + --vscode-problemsErrorIcon-foreground: #f14c4c; + --vscode-problemsWarningIcon-foreground: #cca700; + --vscode-problemsInfoIcon-foreground: #3794ff; + --vscode-charts-foreground: #cccccc; + --vscode-charts-lines: rgba(204, 204, 204, 0.5); + --vscode-charts-red: #f14c4c; + --vscode-charts-blue: #3794ff; + --vscode-charts-yellow: #cca700; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #89d185; + --vscode-charts-purple: #b180d7; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #cccccc; + --vscode-symbolIcon-booleanForeground: #cccccc; + --vscode-symbolIcon-classForeground: #ee9d28; + --vscode-symbolIcon-colorForeground: #cccccc; + --vscode-symbolIcon-constantForeground: #cccccc; + --vscode-symbolIcon-constructorForeground: #b180d7; + --vscode-symbolIcon-enumeratorForeground: #ee9d28; + --vscode-symbolIcon-enumeratorMemberForeground: #75beff; + --vscode-symbolIcon-eventForeground: #ee9d28; + --vscode-symbolIcon-fieldForeground: #75beff; + --vscode-symbolIcon-fileForeground: #cccccc; + --vscode-symbolIcon-folderForeground: #cccccc; + --vscode-symbolIcon-functionForeground: #b180d7; + --vscode-symbolIcon-interfaceForeground: #75beff; + --vscode-symbolIcon-keyForeground: #cccccc; + --vscode-symbolIcon-keywordForeground: #cccccc; + --vscode-symbolIcon-methodForeground: #b180d7; + --vscode-symbolIcon-moduleForeground: #cccccc; + --vscode-symbolIcon-namespaceForeground: #cccccc; + --vscode-symbolIcon-nullForeground: #cccccc; + --vscode-symbolIcon-numberForeground: #cccccc; + --vscode-symbolIcon-objectForeground: #cccccc; + --vscode-symbolIcon-operatorForeground: #cccccc; + --vscode-symbolIcon-packageForeground: #cccccc; + --vscode-symbolIcon-propertyForeground: #cccccc; + --vscode-symbolIcon-referenceForeground: #cccccc; + --vscode-symbolIcon-snippetForeground: #cccccc; + --vscode-symbolIcon-stringForeground: #cccccc; + --vscode-symbolIcon-structForeground: #cccccc; + --vscode-symbolIcon-textForeground: #cccccc; + --vscode-symbolIcon-typeParameterForeground: #cccccc; + --vscode-symbolIcon-unitForeground: #cccccc; + --vscode-symbolIcon-variableForeground: #75beff; + --vscode-actionBar-toggledBackground: rgba(42, 161, 152, 0.24); + --vscode-editorHoverWidget-highlightForeground: #1ebcc5; + --vscode-editor-lineHighlightBackground: #073642; + --vscode-editor-lineHighlightBorder: #282828; + --vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editorCursor-foreground: #d30102; + --vscode-editorWhitespace-foreground: rgba(147, 161, 161, 0.5); + --vscode-editorLineNumber-foreground: #858585; + --vscode-editorIndentGuide-background: rgba(147, 161, 161, 0.5); + --vscode-editorIndentGuide-activeBackground: rgba(195, 225, 225, 0.5); + --vscode-editorIndentGuide-background1: rgba(147, 161, 161, 0.5); + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: rgba(195, 225, 225, 0.5); + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #c6c6c6; + --vscode-editorLineNumber-activeForeground: #949494; + --vscode-editorRuler-foreground: #5a5a5a; + --vscode-editorCodeLens-foreground: #999999; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #888888; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #002b36; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67); + --vscode-editorGhostText-foreground: rgba(255, 255, 255, 0.34); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #cca700; + --vscode-editorOverviewRuler-infoForeground: #3794ff; + --vscode-editorBracketHighlight-foreground1: #cdcdcd; + --vscode-editorBracketHighlight-foreground2: #b58900; + --vscode-editorBracketHighlight-foreground3: #d33682; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #bd9b03; + --vscode-editorUnicodeHighlight-background: rgba(189, 155, 3, 0.15); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(39, 70, 66, 0.3); + --vscode-editorGutter-foldingControlForeground: #c5c5c5; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(0, 68, 84, 0.67); + --vscode-editor-wordHighlightStrongBackground: rgba(0, 90, 111, 0.67); + --vscode-editor-wordHighlightTextBackground: rgba(0, 68, 84, 0.67); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: #00212b; + --vscode-peekViewTitleLabel-foreground: #ffffff; + --vscode-peekViewTitleDescription-foreground: rgba(204, 204, 204, 0.7); + --vscode-peekView-border: #2b2b4a; + --vscode-peekViewResult-background: #00212b; + --vscode-peekViewResult-lineForeground: #bbbbbb; + --vscode-peekViewResult-fileForeground: #ffffff; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #ffffff; + --vscode-peekViewEditor-background: #10192c; + --vscode-peekViewEditorGutter-background: #10192c; + --vscode-peekViewEditorStickyScroll-background: #10192c; + --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3); + --vscode-peekViewEditor-matchHighlightBackground: rgba(119, 68, 170, 0.25); + --vscode-editorMarkerNavigationError-background: #ab395b; + --vscode-editorMarkerNavigationError-headerBackground: rgba(171, 57, 91, 0.1); + --vscode-editorMarkerNavigationWarning-background: #5b7e7a; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(91, 126, 122, 0.1); + --vscode-editorMarkerNavigationInfo-background: #3794ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(55, 148, 255, 0.1); + --vscode-editorMarkerNavigation-background: #002b36; + --vscode-editorSuggestWidget-background: #00212b; + --vscode-editorSuggestWidget-border: #454545; + --vscode-editorSuggestWidget-foreground: #839496; + --vscode-editorSuggestWidget-selectedForeground: #ffffff; + --vscode-editorSuggestWidget-selectedBackground: #005a6f; + --vscode-editorSuggestWidget-highlightForeground: #1ebcc5; + --vscode-editorSuggestWidget-focusHighlightForeground: #1ebcc5; + --vscode-editorSuggestWidgetStatus-foreground: rgba(131, 148, 150, 0.5); + --vscode-tab-activeBackground: #002b37; + --vscode-tab-unfocusedActiveBackground: #002b37; + --vscode-tab-inactiveBackground: #004052; + --vscode-tab-unfocusedInactiveBackground: #004052; + --vscode-tab-activeForeground: #d6dbdb; + --vscode-tab-inactiveForeground: #93a1a1; + --vscode-tab-unfocusedActiveForeground: rgba(214, 219, 219, 0.5); + --vscode-tab-unfocusedInactiveForeground: rgba(147, 161, 161, 0.5); + --vscode-tab-border: #003847; + --vscode-tab-lastPinnedBorder: rgba(42, 161, 152, 0.27); + --vscode-tab-activeModifiedBorder: #3399cc; + --vscode-tab-inactiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 153, 204, 0.5); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 153, 204, 0.25); + --vscode-editorPane-background: #002b36; + --vscode-editorGroupHeader-tabsBackground: #004052; + --vscode-editorGroupHeader-noTabsBackground: #002b36; + --vscode-editorGroup-border: #00212b; + --vscode-editorGroup-dropBackground: rgba(42, 161, 152, 0.27); + --vscode-editorGroup-dropIntoPromptForeground: #cccccc; + --vscode-editorGroup-dropIntoPromptBackground: #00212b; + --vscode-sideBySideEditor-horizontalBorder: #00212b; + --vscode-sideBySideEditor-verticalBorder: #00212b; + --vscode-panel-background: #002b36; + --vscode-panel-border: #2b2b4a; + --vscode-panelTitle-activeForeground: #e7e7e7; + --vscode-panelTitle-inactiveForeground: rgba(231, 231, 231, 0.6); + --vscode-panelTitle-activeBorder: #e7e7e7; + --vscode-panel-dropBorder: #e7e7e7; + --vscode-panelSection-dropBackground: rgba(42, 161, 152, 0.27); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: #2b2b4a; + --vscode-banner-background: #005a6f; + --vscode-banner-foreground: #ffffff; + --vscode-banner-iconForeground: #3794ff; + --vscode-statusBar-foreground: #93a1a1; + --vscode-statusBar-noFolderForeground: #93a1a1; + --vscode-statusBar-background: #00212b; + --vscode-statusBar-noFolderBackground: #00212b; + --vscode-statusBar-focusBorder: #93a1a1; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #93a1a1; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #93a1a1; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #93a1a1; + --vscode-statusBarItem-prominentBackground: #003847; + --vscode-statusBarItem-prominentHoverForeground: #93a1a1; + --vscode-statusBarItem-prominentHoverBackground: #003847; + --vscode-statusBarItem-errorBackground: #ff2626; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #93a1a1; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #7a6400; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #93a1a1; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #003847; + --vscode-activityBar-foreground: #ffffff; + --vscode-activityBar-inactiveForeground: rgba(255, 255, 255, 0.4); + --vscode-activityBar-activeBorder: #ffffff; + --vscode-activityBar-dropBorder: #ffffff; + --vscode-activityBarBadge-background: #007acc; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-profileBadge-background: #4d4d4d; + --vscode-profileBadge-foreground: #ffffff; + --vscode-statusBarItem-remoteBackground: rgba(42, 161, 152, 0.6); + --vscode-statusBarItem-remoteForeground: #ffffff; + --vscode-statusBarItem-remoteHoverForeground: #93a1a1; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #ffffff; + --vscode-statusBarItem-offlineHoverForeground: #93a1a1; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #007acc; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #00212b; + --vscode-sideBarTitle-foreground: #93a1a1; + --vscode-sideBar-dropBackground: rgba(42, 161, 152, 0.27); + --vscode-sideBarSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-titleBar-activeForeground: #cccccc; + --vscode-titleBar-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-titleBar-activeBackground: #002c39; + --vscode-titleBar-inactiveBackground: rgba(0, 44, 57, 0.6); + --vscode-menubar-selectionForeground: #cccccc; + --vscode-menubar-selectionBackground: rgba(90, 93, 94, 0.31); + --vscode-notifications-foreground: #cccccc; + --vscode-notifications-background: #00212b; + --vscode-notificationLink-foreground: #3794ff; + --vscode-notificationCenterHeader-background: #002b38; + --vscode-notifications-border: #002b38; + --vscode-notificationsErrorIcon-foreground: #f14c4c; + --vscode-notificationsWarningIcon-foreground: #cca700; + --vscode-notificationsInfoIcon-foreground: #3794ff; + --vscode-commandCenter-foreground: #cccccc; + --vscode-commandCenter-activeForeground: #cccccc; + --vscode-commandCenter-inactiveForeground: rgba(204, 204, 204, 0.6); + --vscode-commandCenter-background: rgba(255, 255, 255, 0.05); + --vscode-commandCenter-activeBackground: rgba(255, 255, 255, 0.08); + --vscode-commandCenter-border: rgba(204, 204, 204, 0.2); + --vscode-commandCenter-activeBorder: rgba(204, 204, 204, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(204, 204, 204, 0.15); + --vscode-chat-requestBorder: rgba(255, 255, 255, 0.1); + --vscode-chat-slashCommandBackground: #047aa6; + --vscode-chat-slashCommandForeground: #ffffff; + --vscode-simpleFindWidget-sashBorder: #454545; + --vscode-commentsView-resolvedIcon: rgba(204, 204, 204, 0.5); + --vscode-commentsView-unresolvedIcon: rgba(42, 161, 152, 0.6); + --vscode-editorCommentsWidget-resolvedBorder: rgba(204, 204, 204, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: rgba(42, 161, 152, 0.6); + --vscode-editorCommentsWidget-rangeBackground: rgba(42, 161, 152, 0.06); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(42, 161, 152, 0.06); + --vscode-editorGutter-commentRangeForeground: #003845; + --vscode-editorOverviewRuler-commentForeground: #003845; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #003845; + --vscode-editorGutter-commentGlyphForeground: #839496; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #839496; + --vscode-debugToolBar-background: #00212b; + --vscode-debugIcon-startForeground: #89d185; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(122, 189, 122, 0.3); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.2); + --vscode-mergeEditor-changeBase\.background: #4b1818; + --vscode-mergeEditor-changeBase\.word\.background: #6f1313; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: rgba(255, 166, 0, 0.48); + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #e7e7e7; + --vscode-settings-settingsHeaderHoverForeground: rgba(231, 231, 231, 0.7); + --vscode-settings-modifiedItemIndicator: #0c7d9d; + --vscode-settings-headerBorder: #2b2b4a; + --vscode-settings-sashBorder: #2b2b4a; + --vscode-settings-dropdownBackground: #00212b; + --vscode-settings-dropdownForeground: #f0f0f0; + --vscode-settings-dropdownBorder: rgba(42, 161, 152, 0.6); + --vscode-settings-dropdownListBorder: #454545; + --vscode-settings-checkboxBackground: #00212b; + --vscode-settings-checkboxForeground: #f0f0f0; + --vscode-settings-checkboxBorder: rgba(42, 161, 152, 0.6); + --vscode-settings-textInputBackground: #003847; + --vscode-settings-textInputForeground: #93a1a1; + --vscode-settings-numberInputBackground: #003847; + --vscode-settings-numberInputForeground: #93a1a1; + --vscode-settings-focusedRowBackground: rgba(0, 68, 84, 0.4); + --vscode-settings-rowHoverBackground: rgba(0, 68, 84, 0.2); + --vscode-settings-focusedRowBorder: rgba(42, 161, 152, 0.6); + --vscode-terminal-foreground: #cccccc; + --vscode-terminal-selectionBackground: #274642; + --vscode-terminal-inactiveSelectionBackground: rgba(39, 70, 66, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(255, 255, 255, 0.25); + --vscode-terminalCommandDecoration-successBackground: #1b81a8; + --vscode-terminalCommandDecoration-errorBackground: #f14c4c; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: #2b2b4a; + --vscode-terminal-findMatchBackground: #515c6a; + --vscode-terminal-hoverHighlightBackground: rgba(38, 79, 120, 0.13); + --vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(42, 161, 152, 0.27); + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #f14c4c; + --vscode-testing-peekHeaderBackground: rgba(241, 76, 76, 0.1); + --vscode-testing-message\.error\.decorationForeground: #f14c4c; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(131, 148, 150, 0.5); + --vscode-welcomePage-tileBackground: #00212b; + --vscode-welcomePage-tileHoverBackground: #002734; + --vscode-welcomePage-tileBorder: rgba(255, 255, 255, 0.1); + --vscode-welcomePage-progress\.background: #003847; + --vscode-welcomePage-progress\.foreground: #3794ff; + --vscode-walkthrough-stepTitle\.foreground: #ffffff; + --vscode-walkThrough-embeddedEditorBackground: rgba(0, 0, 0, 0.4); + --vscode-inlineChat-background: #00212b; + --vscode-inlineChat-border: #454545; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.36); + --vscode-inlineChat-regionHighlight: rgba(38, 79, 120, 0.25); + --vscode-inlineChatInput-border: #454545; + --vscode-inlineChatInput-focusBorder: rgba(42, 161, 152, 0.6); + --vscode-inlineChatInput-placeholderForeground: rgba(147, 161, 161, 0.67); + --vscode-inlineChatInput-background: #003847; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.1); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-debugExceptionWidget-border: #ab395b; + --vscode-debugExceptionWidget-background: #00212b; + --vscode-ports-iconRunningProcessForeground: #369432; + --vscode-statusBar-debuggingBackground: #00212b; + --vscode-statusBar-debuggingForeground: #93a1a1; + --vscode-editor-inlineValuesForeground: rgba(255, 255, 255, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: #1b81a8; + --vscode-editorGutter-addedBackground: #487e02; + --vscode-editorGutter-deletedBackground: #f14c4c; + --vscode-minimapGutter-modifiedBackground: #1b81a8; + --vscode-minimapGutter-addedBackground: #487e02; + --vscode-minimapGutter-deletedBackground: #f14c4c; + --vscode-editorOverviewRuler-modifiedForeground: rgba(27, 129, 168, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 126, 2, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(241, 76, 76, 0.6); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #ffcc00; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: rgba(0, 68, 84, 0.53); + --vscode-notebook-focusedEditorBorder: rgba(42, 161, 152, 0.6); + --vscode-notebookStatusSuccessIcon-foreground: #89d185; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #89d185; + --vscode-notebookStatusErrorIcon-foreground: #ffeaea; + --vscode-notebookStatusRunningIcon-foreground: #cccccc; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: rgba(0, 68, 84, 0.53); + --vscode-notebook-selectedCellBorder: rgba(0, 68, 84, 0.53); + --vscode-notebook-focusedCellBorder: rgba(42, 161, 152, 0.6); + --vscode-notebook-inactiveFocusedCellBorder: rgba(0, 68, 84, 0.53); + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(255, 255, 255, 0.15); + --vscode-notebook-cellInsertionIndicator: rgba(42, 161, 152, 0.6); + --vscode-notebookScrollbarSlider-background: rgba(121, 121, 121, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(191, 191, 191, 0.4); + --vscode-notebook-symbolHighlightBackground: rgba(255, 255, 255, 0.04); + --vscode-notebook-cellEditorBackground: #00212b; + --vscode-notebook-editorBackground: #002b36; + --vscode-keybindingTable-headerBackground: rgba(204, 204, 204, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(204, 204, 204, 0.04); + --vscode-debugTokenExpression-name: #c586c0; + --vscode-debugTokenExpression-value: rgba(204, 204, 204, 0.6); + --vscode-debugTokenExpression-string: #ce9178; + --vscode-debugTokenExpression-boolean: #4e94ce; + --vscode-debugTokenExpression-number: #b5cea8; + --vscode-debugTokenExpression-error: #f48771; + --vscode-debugView-exceptionLabelForeground: #cccccc; + --vscode-debugView-exceptionLabelBackground: #6c2022; + --vscode-debugView-stateLabelForeground: #cccccc; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #3794ff; + --vscode-debugConsole-warningForeground: #cca700; + --vscode-debugConsole-errorForeground: #ffeaea; + --vscode-debugConsole-sourceForeground: #cccccc; + --vscode-debugConsoleInputIcon-foreground: #cccccc; + --vscode-debugIcon-pauseForeground: #75beff; + --vscode-debugIcon-stopForeground: #f48771; + --vscode-debugIcon-disconnectForeground: #f48771; + --vscode-debugIcon-restartForeground: #89d185; + --vscode-debugIcon-stepOverForeground: #75beff; + --vscode-debugIcon-stepIntoForeground: #75beff; + --vscode-debugIcon-stepOutForeground: #75beff; + --vscode-debugIcon-continueForeground: #75beff; + --vscode-debugIcon-stepBackForeground: #75beff; + --vscode-scm-providerBorder: #454545; + --vscode-extensionButton-background: rgba(42, 161, 152, 0.6); + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: rgba(50, 193, 181, 0.6); + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: rgba(42, 161, 152, 0.6); + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: rgba(50, 193, 181, 0.6); + --vscode-extensionIcon-starForeground: #ff8e00; + --vscode-extensionIcon-verifiedForeground: #3794ff; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #d758b3; + --vscode-terminal-ansiBlack: #073642; + --vscode-terminal-ansiRed: #dc322f; + --vscode-terminal-ansiGreen: #859900; + --vscode-terminal-ansiYellow: #b58900; + --vscode-terminal-ansiBlue: #268bd2; + --vscode-terminal-ansiMagenta: #d33682; + --vscode-terminal-ansiCyan: #2aa198; + --vscode-terminal-ansiWhite: #eee8d5; + --vscode-terminal-ansiBrightBlack: #002b36; + --vscode-terminal-ansiBrightRed: #cb4b16; + --vscode-terminal-ansiBrightGreen: #586e75; + --vscode-terminal-ansiBrightYellow: #657b83; + --vscode-terminal-ansiBrightBlue: #839496; + --vscode-terminal-ansiBrightMagenta: #6c71c4; + --vscode-terminal-ansiBrightCyan: #93a1a1; + --vscode-terminal-ansiBrightWhite: #fdf6e3; + --vscode-interactive-activeCodeBorder: #2b2b4a; + --vscode-interactive-inactiveCodeBorder: rgba(0, 68, 84, 0.53); + --vscode-gitDecoration-addedResourceForeground: #81b88b; + --vscode-gitDecoration-modifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-deletedResourceForeground: #c74e39; + --vscode-gitDecoration-renamedResourceForeground: #73c991; + --vscode-gitDecoration-untrackedResourceForeground: #73c991; + --vscode-gitDecoration-ignoredResourceForeground: #8c8c8c; + --vscode-gitDecoration-stageModifiedResourceForeground: #e2c08d; + --vscode-gitDecoration-stageDeletedResourceForeground: #c74e39; + --vscode-gitDecoration-conflictingResourceForeground: #e4676b; + --vscode-gitDecoration-submoduleResourceForeground: #8db9e2; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/light+.scss b/vendor/mynah-ui/example/src/styles/themes/light+.scss new file mode 100644 index 00000000..4b0ea819 --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/light+.scss @@ -0,0 +1,624 @@ +html:root { + font-size: 13px !important; + --vscode-foreground: #616161; + --vscode-disabledForeground: rgba(97, 97, 97, 0.5); + --vscode-errorForeground: #a1260d; + --vscode-descriptionForeground: #717171; + --vscode-icon-foreground: #424242; + --vscode-focusBorder: #fafbfc; + --vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18); + --vscode-textLink-foreground: #006ab1; + --vscode-textLink-activeForeground: #006ab1; + --vscode-textPreformat-foreground: #a31515; + --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1); + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(220, 220, 220, 0.4); + --vscode-widget-shadow: rgba(0, 0, 0, 0.16); + --vscode-input-background: #ffffff; + --vscode-input-foreground: #616161; + --vscode-input-border: #e1e4e8; + --vscode-inputOption-activeBorder: #007acc; + --vscode-inputOption-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-inputOption-activeBackground: rgba(250, 251, 252, 0.2); + --vscode-inputOption-activeForeground: #000000; + --vscode-input-placeholderForeground: rgba(97, 97, 97, 0.5); + --vscode-inputValidation-infoBackground: #d6ecf2; + --vscode-inputValidation-infoBorder: #007acc; + --vscode-inputValidation-warningBackground: #f6f5d2; + --vscode-inputValidation-warningBorder: #b89500; + --vscode-inputValidation-errorBackground: #f2dede; + --vscode-inputValidation-errorBorder: #be1100; + --vscode-dropdown-background: #ffffff; + --vscode-dropdown-foreground: #616161; + --vscode-dropdown-border: #cecece; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #007acc; + --vscode-button-hoverBackground: #0062a3; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #5f6a79; + --vscode-button-secondaryHoverBackground: #4c5561; + --vscode-badge-background: #c4c4c4; + --vscode-badge-foreground: #333333; + --vscode-scrollbar-shadow: rgba(0, 0, 0, 0); + --vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-progressBar-background: #0e70c0; + --vscode-editorError-foreground: #e51400; + --vscode-editorWarning-foreground: #bf8803; + --vscode-editorInfo-foreground: #1a85ff; + --vscode-editorHint-foreground: #6c6c6c; + --vscode-sash-hoverBorder: #fafbfc; + --vscode-editor-background: #ffffff; + --vscode-editor-foreground: #24292e; + --vscode-editorStickyScroll-background: #ffffff; + --vscode-editorStickyScrollHover-background: #f0f0f0; + --vscode-editorWidget-background: #f3f3f3; + --vscode-editorWidget-foreground: #616161; + --vscode-editorWidget-border: #c8c8c8; + --vscode-quickInput-background: #f3f3f3; + --vscode-quickInput-foreground: #616161; + --vscode-quickInputTitle-background: rgba(0, 0, 0, 0.06); + --vscode-pickerGroup-foreground: #0066bf; + --vscode-pickerGroup-border: #cccedb; + --vscode-keybindingLabel-background: rgba(221, 221, 221, 0.4); + --vscode-keybindingLabel-foreground: #555555; + --vscode-keybindingLabel-border: rgba(204, 204, 204, 0.4); + --vscode-keybindingLabel-bottomBorder: rgba(187, 187, 187, 0.4); + --vscode-editor-selectionBackground: #add6ff; + --vscode-editor-inactiveSelectionBackground: rgba(173, 214, 255, 0.5); + --vscode-editor-selectionHighlightBackground: rgba(219, 237, 255, 0.6); + --vscode-editor-findMatchBackground: #a8ac94; + --vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3); + --vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 0.22); + --vscode-search-resultsInfoForeground: #616161; + --vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15); + --vscode-editorHoverWidget-background: #f3f3f3; + --vscode-editorHoverWidget-foreground: #616161; + --vscode-editorHoverWidget-border: #c8c8c8; + --vscode-editorHoverWidget-statusBarBackground: #e7e7e7; + --vscode-editorLink-activeForeground: #0000ff; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(196, 196, 196, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(196, 196, 196, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(196, 196, 196, 0.1); + --vscode-editorLightBulb-foreground: #ddb100; + --vscode-editorLightBulbAutoFix-foreground: #007acc; + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.25); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(34, 34, 34, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #e4e4e4; + --vscode-diffEditor-unchangedRegionForeground: #4d4c4c; + --vscode-diffEditor-unchangedCodeBackground: rgba(184, 184, 184, 0.16); + --vscode-list-focusOutline: #fafbfc; + --vscode-list-activeSelectionBackground: #0060c0; + --vscode-list-activeSelectionForeground: #ffffff; + --vscode-list-activeSelectionIconForeground: #ffffff; + --vscode-list-inactiveSelectionBackground: #e4e6f1; + --vscode-list-hoverBackground: #f0f0f0; + --vscode-list-dropBackground: #d6ebff; + --vscode-list-highlightForeground: #0066bf; + --vscode-list-focusHighlightForeground: #9dddff; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #b01011; + --vscode-list-warningForeground: #855f00; + --vscode-listFilterWidget-background: #f3f3f3; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.16); + --vscode-list-filterMatchBackground: rgba(234, 92, 0, 0.33); + --vscode-tree-indentGuidesStroke: #a8a8a8; + --vscode-tree-inactiveIndentGuidesStroke: rgba(168, 168, 168, 0.4); + --vscode-tree-tableColumnsBorder: rgba(97, 97, 97, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(97, 97, 97, 0.04); + --vscode-list-deemphasizedForeground: #8e8e90; + --vscode-checkbox-background: #ffffff; + --vscode-checkbox-selectBackground: #f3f3f3; + --vscode-checkbox-foreground: #616161; + --vscode-checkbox-border: #cecece; + --vscode-checkbox-selectBorder: #424242; + --vscode-quickInputList-focusForeground: #ffffff; + --vscode-quickInputList-focusIconForeground: #ffffff; + --vscode-quickInputList-focusBackground: #0060c0; + --vscode-menu-foreground: #616161; + --vscode-menu-background: #ffffff; + --vscode-menu-selectionForeground: #ffffff; + --vscode-menu-selectionBackground: #0060c0; + --vscode-menu-separatorBackground: #d4d4d4; + --vscode-toolbar-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-toolbar-activeBackground: rgba(166, 166, 166, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2); + --vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5); + --vscode-breadcrumb-foreground: #646464; + --vscode-breadcrumb-background: #e1e4e8; + --vscode-breadcrumb-focusForeground: #000000; + --vscode-breadcrumb-activeSelectionForeground: #000000; + --vscode-breadcrumbPicker-background: #f3f3f3; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #c9c9c9; + --vscode-minimap-selectionHighlight: #add6ff; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #bf8803; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(100, 100, 100, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(0, 0, 0, 0.3); + --vscode-problemsErrorIcon-foreground: #e51400; + --vscode-problemsWarningIcon-foreground: #bf8803; + --vscode-problemsInfoIcon-foreground: #1a85ff; + --vscode-charts-foreground: #616161; + --vscode-charts-lines: rgba(97, 97, 97, 0.5); + --vscode-charts-red: #e51400; + --vscode-charts-blue: #1a85ff; + --vscode-charts-yellow: #bf8803; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #388a34; + --vscode-charts-purple: #652d90; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #616161; + --vscode-symbolIcon-booleanForeground: #616161; + --vscode-symbolIcon-classForeground: #d67e00; + --vscode-symbolIcon-colorForeground: #616161; + --vscode-symbolIcon-constantForeground: #616161; + --vscode-symbolIcon-constructorForeground: #652d90; + --vscode-symbolIcon-enumeratorForeground: #d67e00; + --vscode-symbolIcon-enumeratorMemberForeground: #007acc; + --vscode-symbolIcon-eventForeground: #d67e00; + --vscode-symbolIcon-fieldForeground: #007acc; + --vscode-symbolIcon-fileForeground: #616161; + --vscode-symbolIcon-folderForeground: #616161; + --vscode-symbolIcon-functionForeground: #652d90; + --vscode-symbolIcon-interfaceForeground: #007acc; + --vscode-symbolIcon-keyForeground: #616161; + --vscode-symbolIcon-keywordForeground: #616161; + --vscode-symbolIcon-methodForeground: #652d90; + --vscode-symbolIcon-moduleForeground: #616161; + --vscode-symbolIcon-namespaceForeground: #616161; + --vscode-symbolIcon-nullForeground: #616161; + --vscode-symbolIcon-numberForeground: #616161; + --vscode-symbolIcon-objectForeground: #616161; + --vscode-symbolIcon-operatorForeground: #616161; + --vscode-symbolIcon-packageForeground: #616161; + --vscode-symbolIcon-propertyForeground: #616161; + --vscode-symbolIcon-referenceForeground: #616161; + --vscode-symbolIcon-snippetForeground: #616161; + --vscode-symbolIcon-stringForeground: #616161; + --vscode-symbolIcon-structForeground: #616161; + --vscode-symbolIcon-textForeground: #616161; + --vscode-symbolIcon-typeParameterForeground: #616161; + --vscode-symbolIcon-unitForeground: #616161; + --vscode-symbolIcon-variableForeground: #007acc; + --vscode-actionBar-toggledBackground: rgba(250, 251, 252, 0.2); + --vscode-editorHoverWidget-highlightForeground: #0066bf; + --vscode-editor-lineHighlightBorder: #eeeeee; + --vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editorCursor-foreground: #000000; + --vscode-editorWhitespace-foreground: rgba(51, 51, 51, 0.2); + --vscode-editorLineNumber-foreground: #bbbbbb; + --vscode-editorIndentGuide-background: #eeeeee; + --vscode-editorIndentGuide-activeBackground: #a8a8a8; + --vscode-editorIndentGuide-background1: #eeeeee; + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: #a8a8a8; + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #0b216f; + --vscode-editorLineNumber-activeForeground: #575757; + --vscode-editorRuler-foreground: #d3d3d3; + --vscode-editorCodeLens-foreground: #919191; + --vscode-editorBracketMatch-background: #f1f8ff; + --vscode-editorBracketMatch-border: #c8e1ff; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #ffffff; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47); + --vscode-editorGhostText-foreground: rgba(0, 0, 0, 0.47); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #bf8803; + --vscode-editorOverviewRuler-infoForeground: #1a85ff; + --vscode-editorBracketHighlight-foreground1: #0431fa; + --vscode-editorBracketHighlight-foreground2: #319331; + --vscode-editorBracketHighlight-foreground3: #7b3814; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #cea33d; + --vscode-editorUnicodeHighlight-background: rgba(206, 163, 61, 0.08); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(173, 214, 255, 0.3); + --vscode-editorGutter-foldingControlForeground: #424242; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25); + --vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.25); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: #f3f3f3; + --vscode-peekViewTitleLabel-foreground: #000000; + --vscode-peekViewTitleDescription-foreground: #616161; + --vscode-peekView-border: #1a85ff; + --vscode-peekViewResult-background: #f3f3f3; + --vscode-peekViewResult-lineForeground: #646465; + --vscode-peekViewResult-fileForeground: #1e1e1e; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #6c6c6c; + --vscode-peekViewEditor-background: #f2f8fc; + --vscode-peekViewEditorGutter-background: #f2f8fc; + --vscode-peekViewEditorStickyScroll-background: #f2f8fc; + --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3); + --vscode-peekViewEditor-matchHighlightBackground: rgba(0, 0, 0, 0.07); + --vscode-editorMarkerNavigationError-background: #e51400; + --vscode-editorMarkerNavigationError-headerBackground: rgba(229, 20, 0, 0.1); + --vscode-editorMarkerNavigationWarning-background: #bf8803; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(191, 136, 3, 0.1); + --vscode-editorMarkerNavigationInfo-background: #1a85ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(26, 133, 255, 0.1); + --vscode-editorMarkerNavigation-background: #ffffff; + --vscode-editorSuggestWidget-background: #f3f3f3; + --vscode-editorSuggestWidget-border: #c8c8c8; + --vscode-editorSuggestWidget-foreground: #24292e; + --vscode-editorSuggestWidget-selectedForeground: #ffffff; + --vscode-editorSuggestWidget-selectedIconForeground: #ffffff; + --vscode-editorSuggestWidget-selectedBackground: #0060c0; + --vscode-editorSuggestWidget-highlightForeground: #0066bf; + --vscode-editorSuggestWidget-focusHighlightForeground: #9dddff; + --vscode-editorSuggestWidgetStatus-foreground: rgba(36, 41, 46, 0.5); + --vscode-tab-activeBackground: #ffffff; + --vscode-tab-unfocusedActiveBackground: #ffffff; + --vscode-tab-inactiveBackground: #fafbfc; + --vscode-tab-unfocusedInactiveBackground: #fafbfc; + --vscode-tab-activeForeground: #000000; + --vscode-tab-inactiveForeground: rgba(0, 0, 0, 0.67); + --vscode-tab-unfocusedActiveForeground: rgba(0, 0, 0, 0.93); + --vscode-tab-unfocusedInactiveForeground: rgba(0, 0, 0, 0.6); + --vscode-tab-border: #e1e4e8; + --vscode-tab-lastPinnedBorder: #a8a8a8; + --vscode-tab-activeBorderTop: #e36209; + --vscode-tab-unfocusedActiveBorderTop: rgba(9, 158, 227, 0.53); + --vscode-tab-activeModifiedBorder: #33aaee; + --vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, 0.7); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, 0.25); + --vscode-editorPane-background: #e7eaeb; + --vscode-editorGroupHeader-tabsBackground: #f2f4f5; + --vscode-editorGroupHeader-tabsBorder: #e1e4e8; + --vscode-editorGroupHeader-noTabsBackground: #ffffff; + --vscode-editorGroup-border: #e7e7e7; + --vscode-editorGroup-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-editorGroup-dropIntoPromptForeground: #616161; + --vscode-editorGroup-dropIntoPromptBackground: #f3f3f3; + --vscode-sideBySideEditor-horizontalBorder: #e7e7e7; + --vscode-sideBySideEditor-verticalBorder: #e7e7e7; + --vscode-panel-background: #ffffff; + --vscode-panel-border: rgba(128, 128, 128, 0.35); + --vscode-panelTitle-activeForeground: #424242; + --vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, 0.75); + --vscode-panelTitle-activeBorder: #424242; + --vscode-panelInput-border: #e1e4e8; + --vscode-panel-dropBorder: #424242; + --vscode-panelSection-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: rgba(128, 128, 128, 0.35); + --vscode-banner-background: #004386; + --vscode-banner-foreground: #ffffff; + --vscode-banner-iconForeground: #1a85ff; + --vscode-statusBar-foreground: #444444; + --vscode-statusBar-noFolderForeground: #24292e; + --vscode-statusBar-background: #f2f4f5; + --vscode-statusBar-noFolderBackground: #fafbfc; + --vscode-statusBar-border: #e1e4e8; + --vscode-statusBar-focusBorder: #444444; + --vscode-statusBar-noFolderBorder: #e1e4e8; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #444444; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #444444; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #444444; + --vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0); + --vscode-statusBarItem-prominentHoverForeground: #444444; + --vscode-statusBarItem-prominentHoverBackground: #dddddd; + --vscode-statusBarItem-errorBackground: #611708; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #444444; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #725102; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #444444; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #f2f4f5; + --vscode-activityBar-foreground: #8e979c; + --vscode-activityBar-inactiveForeground: rgba(142, 151, 156, 0.4); + --vscode-activityBar-border: #e1e4e8; + --vscode-activityBar-activeBorder: #8e979c; + --vscode-activityBar-activeBackground: #e3e4e4; + --vscode-activityBar-dropBorder: #8e979c; + --vscode-activityBarBadge-background: #54a3ff; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-profileBadge-background: #c4c4c4; + --vscode-profileBadge-foreground: #333333; + --vscode-statusBarItem-remoteBackground: rgba(0, 0, 0, 0); + --vscode-statusBarItem-remoteForeground: #444444; + --vscode-statusBarItem-remoteHoverForeground: #444444; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #444444; + --vscode-statusBarItem-offlineHoverForeground: #444444; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #54a3ff; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #fafbfc; + --vscode-sideBar-foreground: #586069; + --vscode-sideBar-border: #e1e4e8; + --vscode-sideBarTitle-foreground: #24292e; + --vscode-sideBar-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-sideBarSectionHeader-background: #f1f2f3; + --vscode-sideBarSectionHeader-foreground: #24292e; + --vscode-titleBar-activeForeground: #444444; + --vscode-titleBar-inactiveForeground: rgba(68, 68, 68, 0.6); + --vscode-titleBar-activeBackground: #f2f4f5; + --vscode-titleBar-inactiveBackground: rgba(242, 244, 245, 0.6); + --vscode-titleBar-border: #e1e4e8; + --vscode-menubar-selectionForeground: #444444; + --vscode-menubar-selectionBackground: rgba(184, 184, 184, 0.31); + --vscode-notifications-foreground: #616161; + --vscode-notifications-background: #f3f3f3; + --vscode-notificationLink-foreground: #006ab1; + --vscode-notificationCenterHeader-background: #e7e7e7; + --vscode-notifications-border: #e7e7e7; + --vscode-notificationsErrorIcon-foreground: #e51400; + --vscode-notificationsWarningIcon-foreground: #bf8803; + --vscode-notificationsInfoIcon-foreground: #1a85ff; + --vscode-commandCenter-foreground: #444444; + --vscode-commandCenter-activeForeground: #444444; + --vscode-commandCenter-inactiveForeground: rgba(68, 68, 68, 0.6); + --vscode-commandCenter-background: rgba(0, 0, 0, 0.05); + --vscode-commandCenter-activeBackground: rgba(0, 0, 0, 0.08); + --vscode-commandCenter-border: rgba(68, 68, 68, 0.2); + --vscode-commandCenter-activeBorder: rgba(68, 68, 68, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(68, 68, 68, 0.15); + --vscode-chat-requestBorder: rgba(0, 0, 0, 0.1); + --vscode-chat-slashCommandBackground: #c4c4c4; + --vscode-chat-slashCommandForeground: #333333; + --vscode-simpleFindWidget-sashBorder: #c8c8c8; + --vscode-commentsView-resolvedIcon: rgba(97, 97, 97, 0.5); + --vscode-commentsView-unresolvedIcon: #fafbfc; + --vscode-editorCommentsWidget-resolvedBorder: rgba(97, 97, 97, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #fafbfc; + --vscode-editorCommentsWidget-rangeBackground: rgba(250, 251, 252, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(250, 251, 252, 0.1); + --vscode-editorGutter-commentRangeForeground: #d5d8e9; + --vscode-editorOverviewRuler-commentForeground: #d5d8e9; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #d5d8e9; + --vscode-editorGutter-commentGlyphForeground: #24292e; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #24292e; + --vscode-debugToolBar-background: #f3f3f3; + --vscode-debugIcon-startForeground: #388a34; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(206, 231, 206, 0.45); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.4); + --vscode-mergeEditor-changeBase\.background: #ffcccc; + --vscode-mergeEditor-changeBase\.word\.background: #ffa3a3; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #444444; + --vscode-settings-settingsHeaderHoverForeground: rgba(68, 68, 68, 0.7); + --vscode-settings-modifiedItemIndicator: #66afe0; + --vscode-settings-headerBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-sashBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-dropdownBackground: #ffffff; + --vscode-settings-dropdownForeground: #616161; + --vscode-settings-dropdownBorder: #cecece; + --vscode-settings-dropdownListBorder: #c8c8c8; + --vscode-settings-checkboxBackground: #ffffff; + --vscode-settings-checkboxForeground: #616161; + --vscode-settings-checkboxBorder: #cecece; + --vscode-settings-textInputBackground: #ffffff; + --vscode-settings-textInputForeground: #616161; + --vscode-settings-textInputBorder: #e1e4e8; + --vscode-settings-numberInputBackground: #ffffff; + --vscode-settings-numberInputForeground: #616161; + --vscode-settings-numberInputBorder: #e1e4e8; + --vscode-settings-focusedRowBackground: rgba(240, 240, 240, 0.6); + --vscode-settings-rowHoverBackground: rgba(240, 240, 240, 0.3); + --vscode-settings-focusedRowBorder: #fafbfc; + --vscode-terminal-foreground: #333333; + --vscode-terminal-selectionBackground: #add6ff; + --vscode-terminal-inactiveSelectionBackground: rgba(173, 214, 255, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(0, 0, 0, 0.25); + --vscode-terminalCommandDecoration-successBackground: #2090d3; + --vscode-terminalCommandDecoration-errorBackground: #e51400; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: rgba(128, 128, 128, 0.35); + --vscode-terminal-findMatchBackground: #a8ac94; + --vscode-terminal-hoverHighlightBackground: rgba(173, 214, 255, 0.07); + --vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #e51400; + --vscode-testing-peekHeaderBackground: rgba(229, 20, 0, 0.1); + --vscode-testing-message\.error\.decorationForeground: #e51400; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(36, 41, 46, 0.5); + --vscode-welcomePage-tileBackground: #f3f3f3; + --vscode-welcomePage-tileHoverBackground: #dbdbdb; + --vscode-welcomePage-tileBorder: rgba(0, 0, 0, 0.1); + --vscode-welcomePage-progress\.background: #ffffff; + --vscode-welcomePage-progress\.foreground: #006ab1; + --vscode-walkthrough-stepTitle\.foreground: #000000; + --vscode-walkThrough-embeddedEditorBackground: #f4f4f4; + --vscode-inlineChat-background: #f3f3f3; + --vscode-inlineChat-border: #c8c8c8; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.16); + --vscode-inlineChat-regionHighlight: rgba(173, 214, 255, 0.15); + --vscode-inlineChatInput-border: #c8c8c8; + --vscode-inlineChatInput-focusBorder: #fafbfc; + --vscode-inlineChatInput-placeholderForeground: rgba(97, 97, 97, 0.5); + --vscode-inlineChatInput-background: #ffffff; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.13); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-debugExceptionWidget-border: #a31515; + --vscode-debugExceptionWidget-background: #f1dfde; + --vscode-ports-iconRunningProcessForeground: rgba(0, 0, 0, 0); + --vscode-statusBar-debuggingBackground: #fafbfc; + --vscode-statusBar-debuggingForeground: #24292e; + --vscode-statusBar-debuggingBorder: #e1e4e8; + --vscode-editor-inlineValuesForeground: rgba(0, 0, 0, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: #2090d3; + --vscode-editorGutter-addedBackground: #48985d; + --vscode-editorGutter-deletedBackground: #e51400; + --vscode-minimapGutter-modifiedBackground: #2090d3; + --vscode-minimapGutter-addedBackground: #48985d; + --vscode-minimapGutter-deletedBackground: #e51400; + --vscode-editorOverviewRuler-modifiedForeground: rgba(32, 144, 211, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 152, 93, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(229, 20, 0, 0.6); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #be8700; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: #e4e6f1; + --vscode-notebook-focusedEditorBorder: #fafbfc; + --vscode-notebookStatusSuccessIcon-foreground: #388a34; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #388a34; + --vscode-notebookStatusErrorIcon-foreground: #a1260d; + --vscode-notebookStatusRunningIcon-foreground: #616161; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #e4e6f1; + --vscode-notebook-selectedCellBorder: #e4e6f1; + --vscode-notebook-focusedCellBorder: #fafbfc; + --vscode-notebook-inactiveFocusedCellBorder: #e4e6f1; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(0, 0, 0, 0.08); + --vscode-notebook-cellInsertionIndicator: #fafbfc; + --vscode-notebookScrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-notebook-symbolHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-notebook-cellEditorBackground: #fafbfc; + --vscode-notebook-editorBackground: #e7eaeb; + --vscode-keybindingTable-headerBackground: rgba(97, 97, 97, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(97, 97, 97, 0.04); + --vscode-searchEditor-textInputBorder: #e1e4e8; + --vscode-debugTokenExpression-name: #9b46b0; + --vscode-debugTokenExpression-value: rgba(108, 108, 108, 0.8); + --vscode-debugTokenExpression-string: #a31515; + --vscode-debugTokenExpression-boolean: #0000ff; + --vscode-debugTokenExpression-number: #098658; + --vscode-debugTokenExpression-error: #e51400; + --vscode-debugView-exceptionLabelForeground: #ffffff; + --vscode-debugView-exceptionLabelBackground: #a31515; + --vscode-debugView-stateLabelForeground: #616161; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #1a85ff; + --vscode-debugConsole-warningForeground: #bf8803; + --vscode-debugConsole-errorForeground: #a1260d; + --vscode-debugConsole-sourceForeground: #616161; + --vscode-debugConsoleInputIcon-foreground: #616161; + --vscode-debugIcon-pauseForeground: #007acc; + --vscode-debugIcon-stopForeground: #a1260d; + --vscode-debugIcon-disconnectForeground: #a1260d; + --vscode-debugIcon-restartForeground: #388a34; + --vscode-debugIcon-stepOverForeground: #007acc; + --vscode-debugIcon-stepIntoForeground: #007acc; + --vscode-debugIcon-stepOutForeground: #007acc; + --vscode-debugIcon-continueForeground: #007acc; + --vscode-debugIcon-stepBackForeground: #007acc; + --vscode-scm-providerBorder: #c8c8c8; + --vscode-extensionButton-background: #007acc; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #0062a3; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #007acc; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: #0062a3; + --vscode-extensionIcon-starForeground: #df6100; + --vscode-extensionIcon-verifiedForeground: #006ab1; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #b51e78; + --vscode-terminal-ansiBlack: #000000; + --vscode-terminal-ansiRed: #cd3131; + --vscode-terminal-ansiGreen: #00bc00; + --vscode-terminal-ansiYellow: #949800; + --vscode-terminal-ansiBlue: #0451a5; + --vscode-terminal-ansiMagenta: #bc05bc; + --vscode-terminal-ansiCyan: #0598bc; + --vscode-terminal-ansiWhite: #555555; + --vscode-terminal-ansiBrightBlack: #666666; + --vscode-terminal-ansiBrightRed: #cd3131; + --vscode-terminal-ansiBrightGreen: #14ce14; + --vscode-terminal-ansiBrightYellow: #795e26; + --vscode-terminal-ansiBrightBlue: #0451a5; + --vscode-terminal-ansiBrightMagenta: #bc05bc; + --vscode-terminal-ansiBrightCyan: #0598bc; + --vscode-terminal-ansiBrightWhite: #a5a5a5; + --vscode-interactive-activeCodeBorder: #1a85ff; + --vscode-interactive-inactiveCodeBorder: #e4e6f1; + --vscode-gitDecoration-addedResourceForeground: #587c0c; + --vscode-gitDecoration-modifiedResourceForeground: #0073c0; + --vscode-gitDecoration-deletedResourceForeground: #a00000; + --vscode-gitDecoration-renamedResourceForeground: #007100; + --vscode-gitDecoration-untrackedResourceForeground: #66a500; + --vscode-gitDecoration-ignoredResourceForeground: #8e8e90; + --vscode-gitDecoration-stageModifiedResourceForeground: #895503; + --vscode-gitDecoration-stageDeletedResourceForeground: #ad0707; + --vscode-gitDecoration-conflictingResourceForeground: #ff0000; + --vscode-gitDecoration-submoduleResourceForeground: #1258a7; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/light+tweaked.scss b/vendor/mynah-ui/example/src/styles/themes/light+tweaked.scss new file mode 100644 index 00000000..8ea13b9d --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/light+tweaked.scss @@ -0,0 +1,828 @@ +html[theme='light+tweaked']:root { + --vscode-font-family: -apple-system, BlinkMacSystemFont, sans-serif; + --vscode-font-weight: normal; + --vscode-font-size: 13px; + --vscode-editor-font-family: Menlo, Monaco, 'Courier New', monospace; + --vscode-editor-font-weight: normal; + --vscode-editor-font-size: 12px; + --text-link-decoration: none; + --vscode-foreground: #616161; + --vscode-disabledForeground: rgba(97, 97, 97, 0.5); + --vscode-errorForeground: #a1260d; + --vscode-descriptionForeground: #717171; + --vscode-icon-foreground: #424242; + --vscode-focusBorder: #fafbfc; + --vscode-textLink-foreground: #006ab1; + --vscode-textLink-activeForeground: #006ab1; + --vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18); + --vscode-textPreformat-foreground: #a31515; + --vscode-textPreformat-background: rgba(0, 0, 0, 0.1); + --vscode-textBlockQuote-background: #f2f2f2; + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(220, 220, 220, 0.4); + --vscode-sash-hoverBorder: #fafbfc; + --vscode-badge-background: #c4c4c4; + --vscode-badge-foreground: #333333; + --vscode-activityWarningBadge-foreground: #ffffff; + --vscode-activityWarningBadge-background: #bf8803; + --vscode-activityErrorBadge-foreground: #ffffff; + --vscode-activityErrorBadge-background: #e51400; + --vscode-scrollbar-shadow: rgba(0, 0, 0, 0); + --vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-progressBar-background: #0e70c0; + --vscode-chart-line: #236b8e; + --vscode-chart-axis: rgba(0, 0, 0, 0.6); + --vscode-chart-guide: rgba(0, 0, 0, 0.2); + --vscode-editor-background: #ffffff; + --vscode-editor-foreground: #24292e; + --vscode-editorStickyScroll-background: #ffffff; + --vscode-editorStickyScrollHover-background: #f0f0f0; + --vscode-editorStickyScroll-shadow: rgba(0, 0, 0, 0); + --vscode-editorWidget-background: #f3f3f3; + --vscode-editorWidget-foreground: #616161; + --vscode-editorWidget-border: #c8c8c8; + --vscode-editorError-foreground: #e51400; + --vscode-editorWarning-foreground: #bf8803; + --vscode-editorInfo-foreground: #1a85ff; + --vscode-editorHint-foreground: #6c6c6c; + --vscode-editorLink-activeForeground: #0000ff; + --vscode-editor-selectionBackground: #add6ff; + --vscode-editor-inactiveSelectionBackground: rgba(173, 214, 255, 0.5); + --vscode-editor-selectionHighlightBackground: rgba(219, 237, 255, 0.6); + --vscode-editor-compositionBorder: #000000; + --vscode-editor-findMatchBackground: #a8ac94; + --vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3); + --vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15); + --vscode-editorHoverWidget-background: #f3f3f3; + --vscode-editorHoverWidget-foreground: #616161; + --vscode-editorHoverWidget-border: #c8c8c8; + --vscode-editorHoverWidget-statusBarBackground: #e7e7e7; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(196, 196, 196, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(196, 196, 196, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(196, 196, 196, 0.1); + --vscode-editorLightBulb-foreground: #ddb100; + --vscode-editorLightBulbAutoFix-foreground: #007acc; + --vscode-editorLightBulbAi-foreground: #ddb100; + --vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2); + --vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5); + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.25); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(34, 34, 34, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #fafbfc; + --vscode-diffEditor-unchangedRegionForeground: #616161; + --vscode-diffEditor-unchangedCodeBackground: rgba(184, 184, 184, 0.16); + --vscode-widget-shadow: rgba(0, 0, 0, 0.16); + --vscode-toolbar-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-toolbar-activeBackground: rgba(166, 166, 166, 0.31); + --vscode-breadcrumb-foreground: #646464; + --vscode-breadcrumb-background: #e1e4e8; + --vscode-breadcrumb-focusForeground: #000000; + --vscode-breadcrumb-activeSelectionForeground: #000000; + --vscode-breadcrumbPicker-background: #f3f3f3; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-problemsErrorIcon-foreground: #e51400; + --vscode-problemsWarningIcon-foreground: #bf8803; + --vscode-problemsInfoIcon-foreground: #1a85ff; + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #c9c9c9; + --vscode-minimap-selectionHighlight: #add6ff; + --vscode-minimap-infoHighlight: #1a85ff; + --vscode-minimap-warningHighlight: #bf8803; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(100, 100, 100, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(0, 0, 0, 0.3); + --vscode-charts-foreground: #616161; + --vscode-charts-lines: rgba(97, 97, 97, 0.5); + --vscode-charts-red: #e51400; + --vscode-charts-blue: #1a85ff; + --vscode-charts-yellow: #bf8803; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #388a34; + --vscode-charts-purple: #652d90; + --vscode-input-background: #ffffff; + --vscode-input-foreground: #616161; + --vscode-input-border: #e1e4e8; + --vscode-inputOption-activeBorder: #007acc; + --vscode-inputOption-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-inputOption-activeBackground: rgba(250, 251, 252, 0.2); + --vscode-inputOption-activeForeground: #000000; + --vscode-input-placeholderForeground: rgba(97, 97, 97, 0.5); + --vscode-inputValidation-infoBackground: #d6ecf2; + --vscode-inputValidation-infoBorder: #007acc; + --vscode-inputValidation-warningBackground: #f6f5d2; + --vscode-inputValidation-warningBorder: #b89500; + --vscode-inputValidation-errorBackground: #f2dede; + --vscode-inputValidation-errorBorder: #be1100; + --vscode-dropdown-background: #ffffff; + --vscode-dropdown-foreground: #616161; + --vscode-dropdown-border: #cecece; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #007acc; + --vscode-button-hoverBackground: #0062a3; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #5f6a79; + --vscode-button-secondaryHoverBackground: #4c5561; + --vscode-radio-activeForeground: #000000; + --vscode-radio-activeBackground: rgba(250, 251, 252, 0.2); + --vscode-radio-activeBorder: #007acc; + --vscode-radio-inactiveBorder: rgba(0, 0, 0, 0.2); + --vscode-radio-inactiveHoverBackground: rgba(184, 184, 184, 0.31); + --vscode-checkbox-background: #ffffff; + --vscode-checkbox-selectBackground: #f3f3f3; + --vscode-checkbox-foreground: #616161; + --vscode-checkbox-border: #cecece; + --vscode-checkbox-selectBorder: #424242; + --vscode-keybindingLabel-background: rgba(221, 221, 221, 0.4); + --vscode-keybindingLabel-foreground: #555555; + --vscode-keybindingLabel-border: rgba(204, 204, 204, 0.4); + --vscode-keybindingLabel-bottomBorder: rgba(187, 187, 187, 0.4); + --vscode-list-focusOutline: #fafbfc; + --vscode-list-activeSelectionBackground: #0060c0; + --vscode-list-activeSelectionForeground: #ffffff; + --vscode-list-activeSelectionIconForeground: #ffffff; + --vscode-list-inactiveSelectionBackground: #e4e6f1; + --vscode-list-hoverBackground: #f0f0f0; + --vscode-list-dropBackground: #d6ebff; + --vscode-list-dropBetweenBackground: #424242; + --vscode-list-highlightForeground: #0066bf; + --vscode-list-focusHighlightForeground: #9dddff; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #b01011; + --vscode-list-warningForeground: #855f00; + --vscode-listFilterWidget-background: #f3f3f3; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.16); + --vscode-list-filterMatchBackground: rgba(234, 92, 0, 0.33); + --vscode-list-deemphasizedForeground: #8e8e90; + --vscode-tree-indentGuidesStroke: #a8a8a8; + --vscode-tree-inactiveIndentGuidesStroke: rgba(168, 168, 168, 0.4); + --vscode-tree-tableColumnsBorder: rgba(97, 97, 97, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(97, 97, 97, 0.04); + --vscode-editorActionList-background: #f3f3f3; + --vscode-editorActionList-foreground: #616161; + --vscode-editorActionList-focusForeground: #ffffff; + --vscode-editorActionList-focusBackground: #0060c0; + --vscode-menu-foreground: #616161; + --vscode-menu-background: #ffffff; + --vscode-menu-selectionForeground: #ffffff; + --vscode-menu-selectionBackground: #0060c0; + --vscode-menu-separatorBackground: #d4d4d4; + --vscode-quickInput-background: #f3f3f3; + --vscode-quickInput-foreground: #616161; + --vscode-quickInputTitle-background: rgba(0, 0, 0, 0.06); + --vscode-pickerGroup-foreground: #0066bf; + --vscode-pickerGroup-border: #cccedb; + --vscode-quickInputList-focusForeground: #ffffff; + --vscode-quickInputList-focusIconForeground: #ffffff; + --vscode-quickInputList-focusBackground: #0060c0; + --vscode-search-resultsInfoForeground: #616161; + --vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 0.22); + --vscode-editor-lineHighlightBorder: #eeeeee; + --vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editorCursor-foreground: #000000; + --vscode-editorMultiCursor-primary\.foreground: #000000; + --vscode-editorMultiCursor-secondary\.foreground: #000000; + --vscode-editorWhitespace-foreground: rgba(51, 51, 51, 0.2); + --vscode-editorLineNumber-foreground: #bbbbbb; + --vscode-editorIndentGuide-background: #eeeeee; + --vscode-editorIndentGuide-activeBackground: #a8a8a8; + --vscode-editorIndentGuide-background1: #eeeeee; + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: #a8a8a8; + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #0b216f; + --vscode-editorLineNumber-activeForeground: #575757; + --vscode-editorRuler-foreground: #d3d3d3; + --vscode-editorCodeLens-foreground: #919191; + --vscode-editorBracketMatch-background: #f1f8ff; + --vscode-editorBracketMatch-border: #c8e1ff; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #ffffff; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47); + --vscode-editorGhostText-foreground: rgba(0, 0, 0, 0.47); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #bf8803; + --vscode-editorOverviewRuler-infoForeground: #1a85ff; + --vscode-editorBracketHighlight-foreground1: #0431fa; + --vscode-editorBracketHighlight-foreground2: #319331; + --vscode-editorBracketHighlight-foreground3: #7b3814; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #bf8803; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-diffEditor-unchangedRegionShadow: rgba(115, 115, 115, 0.75); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-actionBar-toggledBackground: rgba(250, 251, 252, 0.2); + --vscode-symbolIcon-arrayForeground: #616161; + --vscode-symbolIcon-booleanForeground: #616161; + --vscode-symbolIcon-classForeground: #d67e00; + --vscode-symbolIcon-colorForeground: #616161; + --vscode-symbolIcon-constantForeground: #616161; + --vscode-symbolIcon-constructorForeground: #652d90; + --vscode-symbolIcon-enumeratorForeground: #d67e00; + --vscode-symbolIcon-enumeratorMemberForeground: #007acc; + --vscode-symbolIcon-eventForeground: #d67e00; + --vscode-symbolIcon-fieldForeground: #007acc; + --vscode-symbolIcon-fileForeground: #616161; + --vscode-symbolIcon-folderForeground: #616161; + --vscode-symbolIcon-functionForeground: #652d90; + --vscode-symbolIcon-interfaceForeground: #007acc; + --vscode-symbolIcon-keyForeground: #616161; + --vscode-symbolIcon-keywordForeground: #616161; + --vscode-symbolIcon-methodForeground: #652d90; + --vscode-symbolIcon-moduleForeground: #616161; + --vscode-symbolIcon-namespaceForeground: #616161; + --vscode-symbolIcon-nullForeground: #616161; + --vscode-symbolIcon-numberForeground: #616161; + --vscode-symbolIcon-objectForeground: #616161; + --vscode-symbolIcon-operatorForeground: #616161; + --vscode-symbolIcon-packageForeground: #616161; + --vscode-symbolIcon-propertyForeground: #616161; + --vscode-symbolIcon-referenceForeground: #616161; + --vscode-symbolIcon-snippetForeground: #616161; + --vscode-symbolIcon-stringForeground: #616161; + --vscode-symbolIcon-structForeground: #616161; + --vscode-symbolIcon-textForeground: #616161; + --vscode-symbolIcon-typeParameterForeground: #616161; + --vscode-symbolIcon-unitForeground: #616161; + --vscode-symbolIcon-variableForeground: #007acc; + --vscode-peekViewTitle-background: #f3f3f3; + --vscode-peekViewTitleLabel-foreground: #000000; + --vscode-peekViewTitleDescription-foreground: #616161; + --vscode-peekView-border: #1a85ff; + --vscode-peekViewResult-background: #f3f3f3; + --vscode-peekViewResult-lineForeground: #646465; + --vscode-peekViewResult-fileForeground: #1e1e1e; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #6c6c6c; + --vscode-peekViewEditor-background: #f2f8fc; + --vscode-peekViewEditorGutter-background: #f2f8fc; + --vscode-peekViewEditorStickyScroll-background: #f2f8fc; + --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3); + --vscode-peekViewEditor-matchHighlightBackground: rgba(0, 0, 0, 0.07); + --vscode-editor-foldBackground: rgba(173, 214, 255, 0.3); + --vscode-editor-foldPlaceholderForeground: #808080; + --vscode-editorGutter-foldingControlForeground: #424242; + --vscode-editorSuggestWidget-background: #f3f3f3; + --vscode-editorSuggestWidget-border: #c8c8c8; + --vscode-editorSuggestWidget-foreground: #24292e; + --vscode-editorSuggestWidget-selectedForeground: #ffffff; + --vscode-editorSuggestWidget-selectedIconForeground: #ffffff; + --vscode-editorSuggestWidget-selectedBackground: #0060c0; + --vscode-editorSuggestWidget-highlightForeground: #0066bf; + --vscode-editorSuggestWidget-focusHighlightForeground: #9dddff; + --vscode-editorSuggestWidgetStatus-foreground: rgba(36, 41, 46, 0.5); + --vscode-inlineEdit-originalBackground: rgba(255, 0, 0, 0.04); + --vscode-inlineEdit-modifiedBackground: rgba(156, 204, 44, 0.07); + --vscode-inlineEdit-originalChangedLineBackground: rgba(255, 0, 0, 0.16); + --vscode-inlineEdit-originalChangedTextBackground: rgba(255, 0, 0, 0.16); + --vscode-inlineEdit-modifiedChangedLineBackground: rgba(155, 185, 85, 0.14); + --vscode-inlineEdit-modifiedChangedTextBackground: rgba(156, 204, 44, 0.18); + --vscode-inlineEdit-gutterIndicator\.primaryForeground: #ffffff; + --vscode-inlineEdit-gutterIndicator\.primaryBorder: #007acc; + --vscode-inlineEdit-gutterIndicator\.primaryBackground: rgba(0, 122, 204, 0.5); + --vscode-inlineEdit-gutterIndicator\.secondaryForeground: #ffffff; + --vscode-inlineEdit-gutterIndicator\.secondaryBorder: #5f6a79; + --vscode-inlineEdit-gutterIndicator\.secondaryBackground: #5f6a79; + --vscode-inlineEdit-gutterIndicator\.successfulForeground: #ffffff; + --vscode-inlineEdit-gutterIndicator\.successfulBorder: #007acc; + --vscode-inlineEdit-gutterIndicator\.successfulBackground: #007acc; + --vscode-inlineEdit-gutterIndicator\.background: rgba(95, 95, 95, 0.09); + --vscode-inlineEdit-originalBorder: rgba(255, 0, 0, 0.2); + --vscode-inlineEdit-modifiedBorder: rgba(62, 81, 18, 0.25); + --vscode-inlineEdit-tabWillAcceptModifiedBorder: rgba(62, 81, 18, 0.25); + --vscode-inlineEdit-tabWillAcceptOriginalBorder: rgba(255, 0, 0, 0.2); + --vscode-editorMarkerNavigationError-background: #e51400; + --vscode-editorMarkerNavigationError-headerBackground: rgba(229, 20, 0, 0.1); + --vscode-editorMarkerNavigationWarning-background: #bf8803; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(191, 136, 3, 0.1); + --vscode-editorMarkerNavigationInfo-background: #1a85ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(26, 133, 255, 0.1); + --vscode-editorMarkerNavigation-background: #ffffff; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25); + --vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.25); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-editorHoverWidget-highlightForeground: #0066bf; + --vscode-editor-placeholder\.foreground: rgba(0, 0, 0, 0.47); + --vscode-tab-activeBackground: #ffffff; + --vscode-tab-unfocusedActiveBackground: #ffffff; + --vscode-tab-inactiveBackground: #fafbfc; + --vscode-tab-unfocusedInactiveBackground: #fafbfc; + --vscode-tab-activeForeground: #000000; + --vscode-tab-inactiveForeground: rgba(0, 0, 0, 0.67); + --vscode-tab-unfocusedActiveForeground: rgba(0, 0, 0, 0.93); + --vscode-tab-unfocusedInactiveForeground: rgba(0, 0, 0, 0.6); + --vscode-tab-border: #e1e4e8; + --vscode-tab-lastPinnedBorder: #a8a8a8; + --vscode-tab-activeBorderTop: #e36209; + --vscode-tab-unfocusedActiveBorderTop: rgba(9, 158, 227, 0.53); + --vscode-tab-selectedBorderTop: #e36209; + --vscode-tab-selectedBackground: #ffffff; + --vscode-tab-selectedForeground: #000000; + --vscode-tab-dragAndDropBorder: #000000; + --vscode-tab-activeModifiedBorder: #33aaee; + --vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, 0.7); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, 0.25); + --vscode-editorPane-background: #e7eaeb; + --vscode-editorGroupHeader-tabsBackground: #f2f4f5; + --vscode-editorGroupHeader-tabsBorder: #e1e4e8; + --vscode-editorGroupHeader-noTabsBackground: #ffffff; + --vscode-editorGroup-border: #e7e7e7; + --vscode-editorGroup-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-editorGroup-dropIntoPromptForeground: #616161; + --vscode-editorGroup-dropIntoPromptBackground: #f3f3f3; + --vscode-sideBySideEditor-horizontalBorder: #e7e7e7; + --vscode-sideBySideEditor-verticalBorder: #e7e7e7; + --vscode-banner-background: #004386; + --vscode-banner-foreground: #ffffff; + --vscode-banner-iconForeground: #1a85ff; + --vscode-statusBar-foreground: #444444; + --vscode-statusBar-noFolderForeground: #24292e; + --vscode-statusBar-background: #f2f4f5; + --vscode-statusBar-noFolderBackground: #fafbfc; + --vscode-statusBar-border: #e1e4e8; + --vscode-statusBar-focusBorder: #444444; + --vscode-statusBar-noFolderBorder: #e1e4e8; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #444444; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #444444; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #444444; + --vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0); + --vscode-statusBarItem-prominentHoverForeground: #444444; + --vscode-statusBarItem-prominentHoverBackground: #dddddd; + --vscode-statusBarItem-errorBackground: #611708; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #444444; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #725102; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #444444; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #f2f4f5; + --vscode-activityBar-foreground: #8e979c; + --vscode-activityBar-inactiveForeground: rgba(142, 151, 156, 0.4); + --vscode-activityBar-border: #e1e4e8; + --vscode-activityBar-activeBorder: #8e979c; + --vscode-activityBar-activeBackground: #e3e4e4; + --vscode-activityBar-dropBorder: #8e979c; + --vscode-activityBarBadge-background: #54a3ff; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-activityBarTop-foreground: #424242; + --vscode-activityBarTop-activeBorder: #424242; + --vscode-activityBarTop-inactiveForeground: rgba(66, 66, 66, 0.75); + --vscode-activityBarTop-dropBorder: #424242; + --vscode-panel-background: #ffffff; + --vscode-panel-border: rgba(128, 128, 128, 0.35); + --vscode-panelTitle-activeForeground: #424242; + --vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, 0.75); + --vscode-panelTitle-activeBorder: #424242; + --vscode-panelTitleBadge-background: #54a3ff; + --vscode-panelTitleBadge-foreground: #ffffff; + --vscode-panelInput-border: #e1e4e8; + --vscode-panel-dropBorder: #424242; + --vscode-panelSection-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: rgba(128, 128, 128, 0.35); + --vscode-panelStickyScroll-background: #ffffff; + --vscode-panelStickyScroll-shadow: rgba(0, 0, 0, 0); + --vscode-profileBadge-background: #c4c4c4; + --vscode-profileBadge-foreground: #333333; + --vscode-statusBarItem-remoteBackground: rgba(0, 0, 0, 0); + --vscode-statusBarItem-remoteForeground: #444444; + --vscode-statusBarItem-remoteHoverForeground: #444444; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #444444; + --vscode-statusBarItem-offlineHoverForeground: #444444; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #54a3ff; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #fafbfc; + --vscode-sideBar-foreground: #586069; + --vscode-sideBar-border: #e1e4e8; + --vscode-sideBarTitle-background: #fafbfc; + --vscode-sideBarTitle-foreground: #24292e; + --vscode-sideBar-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-sideBarSectionHeader-background: #f1f2f3; + --vscode-sideBarSectionHeader-foreground: #24292e; + --vscode-sideBarStickyScroll-background: #fafbfc; + --vscode-sideBarStickyScroll-shadow: rgba(0, 0, 0, 0); + --vscode-titleBar-activeForeground: #444444; + --vscode-titleBar-inactiveForeground: rgba(68, 68, 68, 0.6); + --vscode-titleBar-activeBackground: #f2f4f5; + --vscode-titleBar-inactiveBackground: rgba(242, 244, 245, 0.6); + --vscode-titleBar-border: #e1e4e8; + --vscode-menubar-selectionForeground: #444444; + --vscode-menubar-selectionBackground: rgba(184, 184, 184, 0.31); + --vscode-commandCenter-foreground: #444444; + --vscode-commandCenter-activeForeground: #444444; + --vscode-commandCenter-inactiveForeground: rgba(68, 68, 68, 0.6); + --vscode-commandCenter-background: rgba(0, 0, 0, 0.05); + --vscode-commandCenter-activeBackground: rgba(0, 0, 0, 0.08); + --vscode-commandCenter-border: rgba(68, 68, 68, 0.2); + --vscode-commandCenter-activeBorder: rgba(68, 68, 68, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(68, 68, 68, 0.15); + --vscode-notifications-foreground: #616161; + --vscode-notifications-background: #f3f3f3; + --vscode-notificationLink-foreground: #006ab1; + --vscode-notificationCenterHeader-background: #e7e7e7; + --vscode-notifications-border: #e7e7e7; + --vscode-notificationsErrorIcon-foreground: #e51400; + --vscode-notificationsWarningIcon-foreground: #bf8803; + --vscode-notificationsInfoIcon-foreground: #1a85ff; + --vscode-debugToolBar-background: #f3f3f3; + --vscode-debugIcon-startForeground: #388a34; + --vscode-inlineChat-foreground: #616161; + --vscode-inlineChat-background: #f3f3f3; + --vscode-inlineChat-border: #c8c8c8; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.16); + --vscode-inlineChatInput-border: #c8c8c8; + --vscode-inlineChatInput-focusBorder: #fafbfc; + --vscode-inlineChatInput-placeholderForeground: rgba(97, 97, 97, 0.5); + --vscode-inlineChatInput-background: #ffffff; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.13); + --vscode-editorOverviewRuler-inlineChatInserted: rgba(156, 204, 44, 0.2); + --vscode-editorMinimap-inlineChatInserted: rgba(156, 204, 44, 0.2); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-editorOverviewRuler-inlineChatRemoved: rgba(255, 0, 0, 0.16); + --vscode-editorWatermark-foreground: rgba(36, 41, 46, 0.68); + --vscode-extensionButton-background: #007acc; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #0062a3; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #007acc; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: #0062a3; + --vscode-extensionIcon-verifiedForeground: #006ab1; + --vscode-chat-requestBorder: rgba(0, 0, 0, 0.1); + --vscode-chat-requestBackground: rgba(255, 255, 255, 0.62); + --vscode-chat-slashCommandBackground: rgba(210, 236, 255, 0.6); + --vscode-chat-slashCommandForeground: #306ca2; + --vscode-chat-avatarBackground: #f2f2f2; + --vscode-chat-avatarForeground: #616161; + --vscode-chat-editedFileForeground: #895503; + --vscode-commentsView-resolvedIcon: rgba(97, 97, 97, 0.5); + --vscode-commentsView-unresolvedIcon: #fafbfc; + --vscode-editorCommentsWidget-replyInputBackground: #f3f3f3; + --vscode-editorCommentsWidget-resolvedBorder: rgba(97, 97, 97, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #fafbfc; + --vscode-editorCommentsWidget-rangeBackground: rgba(250, 251, 252, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(250, 251, 252, 0.1); + --vscode-notebook-cellBorderColor: #e4e6f1; + --vscode-notebook-focusedEditorBorder: #fafbfc; + --vscode-notebookStatusSuccessIcon-foreground: #388a34; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #388a34; + --vscode-notebookStatusErrorIcon-foreground: #a1260d; + --vscode-notebookStatusRunningIcon-foreground: #616161; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #e4e6f1; + --vscode-notebook-selectedCellBorder: #e4e6f1; + --vscode-notebook-focusedCellBorder: #fafbfc; + --vscode-notebook-inactiveFocusedCellBorder: #e4e6f1; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(0, 0, 0, 0.08); + --vscode-notebook-cellInsertionIndicator: #fafbfc; + --vscode-notebookScrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-notebook-symbolHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-notebook-cellEditorBackground: #fafbfc; + --vscode-notebook-editorBackground: #e7eaeb; + --vscode-editorGutter-modifiedBackground: #2090d3; + --vscode-editorGutter-addedBackground: #48985d; + --vscode-editorGutter-deletedBackground: #e51400; + --vscode-minimapGutter-modifiedBackground: #2090d3; + --vscode-minimapGutter-addedBackground: #48985d; + --vscode-minimapGutter-deletedBackground: #e51400; + --vscode-editorOverviewRuler-modifiedForeground: rgba(32, 144, 211, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 152, 93, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(229, 20, 0, 0.6); + --vscode-editorGutter-itemGlyphForeground: #24292e; + --vscode-editorGutter-itemBackground: #d5d8e9; + --vscode-terminal-foreground: #333333; + --vscode-terminal-selectionBackground: #add6ff; + --vscode-terminal-inactiveSelectionBackground: rgba(173, 214, 255, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(0, 0, 0, 0.25); + --vscode-terminalCommandDecoration-successBackground: #2090d3; + --vscode-terminalCommandDecoration-errorBackground: #e51400; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: rgba(128, 128, 128, 0.35); + --vscode-terminalOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-terminal-findMatchBackground: #a8ac94; + --vscode-terminal-hoverHighlightBackground: rgba(173, 214, 255, 0.07); + --vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(38, 119, 203, 0.18); + --vscode-terminal-initialHintForeground: rgba(0, 0, 0, 0.47); + --vscode-scmGraph-historyItemRefColor: #1a85ff; + --vscode-scmGraph-historyItemRemoteRefColor: #652d90; + --vscode-scmGraph-historyItemBaseRefColor: #ea5c00; + --vscode-scmGraph-historyItemHoverDefaultLabelForeground: #616161; + --vscode-scmGraph-historyItemHoverDefaultLabelBackground: #c4c4c4; + --vscode-scmGraph-historyItemHoverLabelForeground: #ffffff; + --vscode-scmGraph-historyItemHoverAdditionsForeground: #587c0c; + --vscode-scmGraph-historyItemHoverDeletionsForeground: #ad0707; + --vscode-scmGraph-foreground1: #ffb000; + --vscode-scmGraph-foreground2: #dc267f; + --vscode-scmGraph-foreground3: #994f00; + --vscode-scmGraph-foreground4: #40b0a6; + --vscode-scmGraph-foreground5: #b66dff; + --vscode-editorGutter-commentRangeForeground: #d5d8e9; + --vscode-editorOverviewRuler-commentForeground: #d5d8e9; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #d5d8e9; + --vscode-editorGutter-commentGlyphForeground: #24292e; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #24292e; + --vscode-ports-iconRunningProcessForeground: rgba(0, 0, 0, 0); + --vscode-settings-headerForeground: #444444; + --vscode-settings-settingsHeaderHoverForeground: rgba(68, 68, 68, 0.7); + --vscode-settings-modifiedItemIndicator: #66afe0; + --vscode-settings-headerBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-sashBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-dropdownBackground: #ffffff; + --vscode-settings-dropdownForeground: #616161; + --vscode-settings-dropdownBorder: #cecece; + --vscode-settings-dropdownListBorder: #c8c8c8; + --vscode-settings-checkboxBackground: #ffffff; + --vscode-settings-checkboxForeground: #616161; + --vscode-settings-checkboxBorder: #cecece; + --vscode-settings-textInputBackground: #ffffff; + --vscode-settings-textInputForeground: #616161; + --vscode-settings-textInputBorder: #e1e4e8; + --vscode-settings-numberInputBackground: #ffffff; + --vscode-settings-numberInputForeground: #616161; + --vscode-settings-numberInputBorder: #e1e4e8; + --vscode-settings-focusedRowBackground: rgba(240, 240, 240, 0.6); + --vscode-settings-rowHoverBackground: rgba(240, 240, 240, 0.3); + --vscode-settings-focusedRowBorder: #fafbfc; + --vscode-keybindingTable-headerBackground: rgba(97, 97, 97, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(97, 97, 97, 0.04); + --vscode-debugExceptionWidget-border: #a31515; + --vscode-debugExceptionWidget-background: #f1dfde; + --vscode-editor-inlineValuesForeground: rgba(0, 0, 0, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #be8700; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(206, 231, 206, 0.45); + --vscode-multiDiffEditor-headerBackground: #fafbfc; + --vscode-multiDiffEditor-background: #ffffff; + --vscode-multiDiffEditor-border: #cccccc; + --vscode-minimap-chatEditHighlight: rgba(255, 255, 255, 0.6); + --vscode-gauge-background: #007acc; + --vscode-gauge-foreground: rgba(0, 122, 204, 0.3); + --vscode-gauge-warningBackground: #b89500; + --vscode-gauge-warningForeground: rgba(184, 149, 0, 0.3); + --vscode-gauge-errorBackground: #be1100; + --vscode-gauge-errorForeground: rgba(190, 17, 0, 0.3); + --vscode-interactive-activeCodeBorder: #007acc; + --vscode-interactive-inactiveCodeBorder: #e4e6f1; + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #e51400; + --vscode-testing-messagePeekBorder: #1a85ff; + --vscode-testing-peekHeaderBackground: rgba(229, 20, 0, 0.1); + --vscode-testing-messagePeekHeaderBackground: rgba(26, 133, 255, 0.1); + --vscode-testing-coveredBackground: rgba(156, 204, 44, 0.25); + --vscode-testing-coveredBorder: rgba(156, 204, 44, 0.19); + --vscode-testing-coveredGutterBackground: rgba(156, 204, 44, 0.15); + --vscode-testing-uncoveredBranchBackground: #ff9999; + --vscode-testing-uncoveredBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-uncoveredBorder: rgba(255, 0, 0, 0.15); + --vscode-testing-uncoveredGutterBackground: rgba(255, 0, 0, 0.3); + --vscode-testing-coverCountBadgeBackground: #c4c4c4; + --vscode-testing-coverCountBadgeForeground: #333333; + --vscode-testing-message\.error\.badgeBackground: #e51400; + --vscode-testing-message\.error\.badgeBorder: #e51400; + --vscode-testing-message\.error\.badgeForeground: #ffffff; + --vscode-testing-message\.info\.decorationForeground: rgba(36, 41, 46, 0.5); + --vscode-testing-iconErrored\.retired: rgba(241, 76, 76, 0.7); + --vscode-testing-iconFailed\.retired: rgba(241, 76, 76, 0.7); + --vscode-testing-iconPassed\.retired: rgba(115, 201, 145, 0.7); + --vscode-testing-iconQueued\.retired: rgba(204, 167, 0, 0.7); + --vscode-testing-iconUnset\.retired: rgba(132, 132, 132, 0.7); + --vscode-testing-iconSkipped\.retired: rgba(132, 132, 132, 0.7); + --vscode-searchEditor-textInputBorder: #e1e4e8; + --vscode-statusBar-debuggingBackground: #fafbfc; + --vscode-statusBar-debuggingForeground: #24292e; + --vscode-statusBar-debuggingBorder: #e1e4e8; + --vscode-commandCenter-debuggingBackground: rgba(250, 251, 252, 0.26); + --vscode-debugTokenExpression-name: #9b46b0; + --vscode-debugTokenExpression-type: #4a90e2; + --vscode-debugTokenExpression-value: rgba(108, 108, 108, 0.8); + --vscode-debugTokenExpression-string: #a31515; + --vscode-debugTokenExpression-boolean: #0000ff; + --vscode-debugTokenExpression-number: #098658; + --vscode-debugTokenExpression-error: #e51400; + --vscode-debugView-exceptionLabelForeground: #ffffff; + --vscode-debugView-exceptionLabelBackground: #a31515; + --vscode-debugView-stateLabelForeground: #616161; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #1a85ff; + --vscode-debugConsole-warningForeground: #bf8803; + --vscode-debugConsole-errorForeground: #a1260d; + --vscode-debugConsole-sourceForeground: #616161; + --vscode-debugConsoleInputIcon-foreground: #616161; + --vscode-debugIcon-pauseForeground: #007acc; + --vscode-debugIcon-stopForeground: #a1260d; + --vscode-debugIcon-disconnectForeground: #a1260d; + --vscode-debugIcon-restartForeground: #388a34; + --vscode-debugIcon-stepOverForeground: #007acc; + --vscode-debugIcon-stepIntoForeground: #007acc; + --vscode-debugIcon-stepOutForeground: #007acc; + --vscode-debugIcon-continueForeground: #007acc; + --vscode-debugIcon-stepBackForeground: #007acc; + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.4); + --vscode-mergeEditor-changeBase\.background: #ffcccc; + --vscode-mergeEditor-changeBase\.word\.background: #ffa3a3; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-extensionIcon-starForeground: #df6100; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #b51e78; + --vscode-extensionIcon-privateForeground: rgba(0, 0, 0, 0.38); + --vscode-terminal-ansiBlack: #000000; + --vscode-terminal-ansiRed: #cd3131; + --vscode-terminal-ansiGreen: #107c10; + --vscode-terminal-ansiYellow: #949800; + --vscode-terminal-ansiBlue: #0451a5; + --vscode-terminal-ansiMagenta: #bc05bc; + --vscode-terminal-ansiCyan: #0598bc; + --vscode-terminal-ansiWhite: #555555; + --vscode-terminal-ansiBrightBlack: #666666; + --vscode-terminal-ansiBrightRed: #cd3131; + --vscode-terminal-ansiBrightGreen: #14ce14; + --vscode-terminal-ansiBrightYellow: #795e26; + --vscode-terminal-ansiBrightBlue: #0451a5; + --vscode-terminal-ansiBrightMagenta: #bc05bc; + --vscode-terminal-ansiBrightCyan: #0598bc; + --vscode-terminal-ansiBrightWhite: #a5a5a5; + --vscode-simpleFindWidget-sashBorder: #c8c8c8; + --vscode-terminalStickyScrollHover-background: #f0f0f0; + --vscode-terminalCommandGuide-foreground: #e4e6f1; + --vscode-terminalSymbolIcon-flagForeground: #d67e00; + --vscode-terminalSymbolIcon-aliasForeground: #652d90; + --vscode-terminalSymbolIcon-optionValueForeground: #007acc; + --vscode-terminalSymbolIcon-methodForeground: #652d90; + --vscode-terminalSymbolIcon-argumentForeground: #007acc; + --vscode-terminalSymbolIcon-optionForeground: #d67e00; + --vscode-terminalSymbolIcon-fileForeground: #616161; + --vscode-terminalSymbolIcon-folderForeground: #616161; + --vscode-welcomePage-tileBackground: #f3f3f3; + --vscode-welcomePage-tileHoverBackground: #dbdbdb; + --vscode-welcomePage-tileBorder: rgba(0, 0, 0, 0.1); + --vscode-welcomePage-progress\.background: #ffffff; + --vscode-welcomePage-progress\.foreground: #006ab1; + --vscode-walkthrough-stepTitle\.foreground: #000000; + --vscode-walkThrough-embeddedEditorBackground: #f4f4f4; + --vscode-profiles-sashBorder: rgba(128, 128, 128, 0.35); + --vscode-gitDecoration-addedResourceForeground: #587c0c; + --vscode-gitDecoration-modifiedResourceForeground: #0073c0; + --vscode-gitDecoration-deletedResourceForeground: #a00000; + --vscode-gitDecoration-renamedResourceForeground: #007100; + --vscode-gitDecoration-untrackedResourceForeground: #66a500; + --vscode-gitDecoration-ignoredResourceForeground: #8e8e90; + --vscode-gitDecoration-stageModifiedResourceForeground: #895503; + --vscode-gitDecoration-stageDeletedResourceForeground: #ad0707; + --vscode-gitDecoration-conflictingResourceForeground: #ff0000; + --vscode-gitDecoration-submoduleResourceForeground: #1258a7; + --vscode-git-blame\.editorDecorationForeground: #969696; + --vscode-gitlens-gutterBackgroundColor: rgba(0, 0, 0, 0.05); + --vscode-gitlens-gutterForegroundColor: #747474; + --vscode-gitlens-gutterUncommittedForegroundColor: rgba(0, 188, 242, 0.6); + --vscode-gitlens-trailingLineBackgroundColor: rgba(0, 0, 0, 0); + --vscode-gitlens-trailingLineForegroundColor: rgba(153, 153, 153, 0.35); + --vscode-gitlens-lineHighlightBackgroundColor: rgba(0, 188, 242, 0.2); + --vscode-gitlens-lineHighlightOverviewRulerColor: rgba(0, 188, 242, 0.6); + --vscode-gitlens-openAutolinkedIssueIconColor: #1a7f37; + --vscode-gitlens-closedAutolinkedIssueIconColor: #8250df; + --vscode-gitlens-closedPullRequestIconColor: #cf222e; + --vscode-gitlens-openPullRequestIconColor: #1a7f37; + --vscode-gitlens-mergedPullRequestIconColor: #8250df; + --vscode-gitlens-unpublishedChangesIconColor: #35b15e; + --vscode-gitlens-unpublishedCommitIconColor: #35b15e; + --vscode-gitlens-unpulledChangesIconColor: #b15e35; + --vscode-gitlens-decorations\.addedForegroundColor: #587c0c; + --vscode-gitlens-decorations\.copiedForegroundColor: #007100; + --vscode-gitlens-decorations\.deletedForegroundColor: #a00000; + --vscode-gitlens-decorations\.ignoredForegroundColor: #8e8e90; + --vscode-gitlens-decorations\.modifiedForegroundColor: #0073c0; + --vscode-gitlens-decorations\.untrackedForegroundColor: #66a500; + --vscode-gitlens-decorations\.renamedForegroundColor: #007100; + --vscode-gitlens-decorations\.branchAheadForegroundColor: #35b15e; + --vscode-gitlens-decorations\.branchBehindForegroundColor: #b15e35; + --vscode-gitlens-decorations\.branchDivergedForegroundColor: #d8af1b; + --vscode-gitlens-decorations\.branchUpToDateForegroundColor: #586069; + --vscode-gitlens-decorations\.branchUnpublishedForegroundColor: #586069; + --vscode-gitlens-decorations\.branchMissingUpstreamForegroundColor: #ad0707; + --vscode-gitlens-decorations\.statusMergingOrRebasingConflictForegroundColor: #ad0707; + --vscode-gitlens-decorations\.statusMergingOrRebasingForegroundColor: #d8af1b; + --vscode-gitlens-decorations\.workspaceRepoMissingForegroundColor: #949494; + --vscode-gitlens-decorations\.workspaceCurrentForegroundColor: #35b15e; + --vscode-gitlens-decorations\.workspaceRepoOpenForegroundColor: #35b15e; + --vscode-gitlens-decorations\.worktreeHasUncommittedChangesForegroundColor: #895503; + --vscode-gitlens-decorations\.worktreeMissingForegroundColor: #ad0707; + --vscode-gitlens-graphLane1Color: #15a0bf; + --vscode-gitlens-graphLane2Color: #0669f7; + --vscode-gitlens-graphLane3Color: #8e00c2; + --vscode-gitlens-graphLane4Color: #c517b6; + --vscode-gitlens-graphLane5Color: #d90171; + --vscode-gitlens-graphLane6Color: #cd0101; + --vscode-gitlens-graphLane7Color: #f25d2e; + --vscode-gitlens-graphLane8Color: #f2ca33; + --vscode-gitlens-graphLane9Color: #7bd938; + --vscode-gitlens-graphLane10Color: #2ece9d; + --vscode-gitlens-graphChangesColumnAddedColor: #2da44e; + --vscode-gitlens-graphChangesColumnDeletedColor: #cf222e; + --vscode-gitlens-graphMinimapMarkerHeadColor: #04c814; + --vscode-gitlens-graphScrollMarkerHeadColor: #04c814; + --vscode-gitlens-graphMinimapMarkerUpstreamColor: #8cd993; + --vscode-gitlens-graphScrollMarkerUpstreamColor: #8cd993; + --vscode-gitlens-graphMinimapMarkerHighlightsColor: #f5cc00; + --vscode-gitlens-graphScrollMarkerHighlightsColor: #f5cc00; + --vscode-gitlens-graphMinimapMarkerLocalBranchesColor: #3095e8; + --vscode-gitlens-graphScrollMarkerLocalBranchesColor: #3095e8; + --vscode-gitlens-graphMinimapMarkerPullRequestsColor: #ff8f18; + --vscode-gitlens-graphScrollMarkerPullRequestsColor: #ff8f18; + --vscode-gitlens-graphMinimapMarkerRemoteBranchesColor: #67ace4; + --vscode-gitlens-graphScrollMarkerRemoteBranchesColor: #67ace4; + --vscode-gitlens-graphMinimapMarkerStashesColor: #e467e4; + --vscode-gitlens-graphScrollMarkerStashesColor: #e467e4; + --vscode-gitlens-graphMinimapMarkerTagsColor: #d2a379; + --vscode-gitlens-graphScrollMarkerTagsColor: #d2a379; + --vscode-gitlens-launchpadIndicatorMergeableColor: #42c954; + --vscode-gitlens-launchpadIndicatorMergeableHoverColor: #42c954; + --vscode-gitlens-launchpadIndicatorBlockedColor: #ad0707; + --vscode-gitlens-launchpadIndicatorBlockedHoverColor: #ad0707; + --vscode-gitlens-launchpadIndicatorAttentionColor: #cc9b15; + --vscode-gitlens-launchpadIndicatorAttentionHoverColor: #cc9b15; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/light-orange.scss b/vendor/mynah-ui/example/src/styles/themes/light-orange.scss new file mode 100644 index 00000000..3a76519d --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/light-orange.scss @@ -0,0 +1,638 @@ +html[theme='light-orange']:root { + --vscode-foreground: #434343; + --vscode-disabledForeground: rgba(97, 97, 97, 0.5); + --vscode-errorForeground: #ff0000; + --vscode-descriptionForeground: #2e2e2e; + --vscode-icon-foreground: #424242; + --vscode-focusBorder: #ffffff; + --vscode-selection-background: rgba(255, 102, 0, 0.28); + --vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18); + --vscode-textLink-foreground: #006ab1; + --vscode-textLink-activeForeground: #006ab1; + --vscode-textPreformat-foreground: #a31515; + --vscode-textBlockQuote-background: rgba(255, 0, 0, 0); + --vscode-textBlockQuote-border: rgba(255, 0, 0, 0); + --vscode-textCodeBlock-background: rgba(255, 0, 0, 0); + --vscode-widget-shadow: #dddddd; + --vscode-input-background: #ffffff; + --vscode-input-foreground: #222222; + --vscode-input-border: #bfbfbf; + --vscode-inputOption-activeBorder: #007acc; + --vscode-inputOption-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-inputOption-activeBackground: #e7e7e7; + --vscode-inputOption-activeForeground: #000000; + --vscode-input-placeholderForeground: rgba(67, 67, 67, 0.5); + --vscode-inputValidation-infoBackground: #d6ecf2; + --vscode-inputValidation-infoBorder: #007acc; + --vscode-inputValidation-warningBackground: #f6f5d2; + --vscode-inputValidation-warningBorder: #b89500; + --vscode-inputValidation-errorBackground: #f2dede; + --vscode-inputValidation-errorBorder: #be1100; + --vscode-dropdown-background: #ffffff; + --vscode-dropdown-listBackground: #ffffff; + --vscode-dropdown-foreground: #222222; + --vscode-dropdown-border: #bfbfbf; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #ff6600; + --vscode-button-hoverBackground: #ff6600; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #5f6a79; + --vscode-button-secondaryHoverBackground: #4c5561; + --vscode-badge-background: #ff6600; + --vscode-badge-foreground: #ffffff; + --vscode-scrollbar-shadow: #dddddd; + --vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-progressBar-background: #0e70c0; + --vscode-editorError-foreground: #e51400; + --vscode-editorWarning-foreground: #bf8803; + --vscode-editorInfo-foreground: #1a85ff; + --vscode-editorHint-foreground: #6c6c6c; + --vscode-sash-hoverBorder: #ffffff; + --vscode-editor-background: #ffffff; + --vscode-editor-foreground: #000000; + --vscode-editorStickyScroll-background: #ffffff; + --vscode-editorStickyScrollHover-background: #f0f0f0; + --vscode-editorWidget-background: #f2f2f2; + --vscode-editorWidget-foreground: #434343; + --vscode-editorWidget-border: #c8c8c8; + --vscode-quickInput-background: #f2f2f2; + --vscode-quickInput-foreground: #434343; + --vscode-quickInputTitle-background: rgba(0, 0, 0, 0.06); + --vscode-pickerGroup-foreground: #0066bf; + --vscode-pickerGroup-border: #cccedb; + --vscode-keybindingLabel-background: rgba(221, 221, 221, 0.4); + --vscode-keybindingLabel-foreground: #555555; + --vscode-keybindingLabel-border: rgba(204, 204, 204, 0.4); + --vscode-keybindingLabel-bottomBorder: rgba(187, 187, 187, 0.4); + --vscode-editor-selectionBackground: rgba(255, 102, 0, 0.27); + --vscode-editor-inactiveSelectionBackground: rgba(255, 102, 0, 0.13); + --vscode-editor-selectionHighlightBackground: rgba(255, 134, 53, 0.16); + --vscode-editor-findMatchBackground: #a8ac94; + --vscode-editor-findMatchHighlightBackground: rgba(255, 102, 0, 0.27); + --vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3); + --vscode-searchEditor-findMatchBackground: rgba(255, 102, 0, 0.18); + --vscode-search-resultsInfoForeground: #434343; + --vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15); + --vscode-editorHoverWidget-background: #f2f2f2; + --vscode-editorHoverWidget-foreground: #434343; + --vscode-editorHoverWidget-border: #c8c8c8; + --vscode-editorHoverWidget-statusBarBackground: #e6e6e6; + --vscode-editorLink-activeForeground: #0000ff; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(255, 102, 0, 0.1); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(255, 102, 0, 0.1); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(255, 102, 0, 0.1); + --vscode-editorLightBulb-foreground: #ddb100; + --vscode-editorLightBulbAutoFix-foreground: #007acc; + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.25); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(34, 34, 34, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #e4e4e4; + --vscode-diffEditor-unchangedRegionForeground: #4d4c4c; + --vscode-diffEditor-unchangedCodeBackground: rgba(184, 184, 184, 0.16); + --vscode-list-focusBackground: rgba(222, 220, 222, 0.67); + --vscode-list-focusForeground: #000000; + --vscode-list-focusOutline: #ffffff; + --vscode-list-activeSelectionBackground: #cedeef; + --vscode-list-activeSelectionForeground: #ff6600; + --vscode-list-activeSelectionIconForeground: #ffffff; + --vscode-list-inactiveSelectionBackground: #dbdbdb; + --vscode-list-inactiveSelectionForeground: #ff6600; + --vscode-list-inactiveFocusBackground: #dbdbdb; + --vscode-list-hoverBackground: #f0f0f0; + --vscode-list-hoverForeground: #ff6600; + --vscode-list-dropBackground: #dedcde; + --vscode-list-highlightForeground: #2e2e2e; + --vscode-list-focusHighlightForeground: #2e2e2e; + --vscode-list-invalidItemForeground: #2e2e2e; + --vscode-list-errorForeground: #2e2e2e; + --vscode-list-warningForeground: #2e2e2e; + --vscode-listFilterWidget-background: #f2f2f2; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: #dddddd; + --vscode-list-filterMatchBackground: rgba(255, 102, 0, 0.27); + --vscode-tree-indentGuidesStroke: #a9a9a9; + --vscode-tree-inactiveIndentGuidesStroke: rgba(169, 169, 169, 0.4); + --vscode-tree-tableColumnsBorder: rgba(97, 97, 97, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(67, 67, 67, 0.04); + --vscode-list-deemphasizedForeground: #8e8e90; + --vscode-checkbox-background: #ffffff; + --vscode-checkbox-selectBackground: #f2f2f2; + --vscode-checkbox-foreground: #222222; + --vscode-checkbox-border: #bfbfbf; + --vscode-checkbox-selectBorder: #424242; + --vscode-quickInputList-focusForeground: #ff6600; + --vscode-quickInputList-focusIconForeground: #ffffff; + --vscode-quickInputList-focusBackground: #cedeef; + --vscode-menu-foreground: #222222; + --vscode-menu-background: #ffffff; + --vscode-menu-selectionForeground: #ffffff; + --vscode-menu-selectionBackground: #ff6600; + --vscode-menu-separatorBackground: #d4d4d4; + --vscode-toolbar-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-toolbar-activeBackground: rgba(166, 166, 166, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2); + --vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5); + --vscode-breadcrumb-foreground: rgba(67, 67, 67, 0.8); + --vscode-breadcrumb-background: #ffffff; + --vscode-breadcrumb-focusForeground: #363636; + --vscode-breadcrumb-activeSelectionForeground: #363636; + --vscode-breadcrumbPicker-background: #f2f2f2; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #c9c9c9; + --vscode-minimap-selectionHighlight: #add6ff; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #bf8803; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(100, 100, 100, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(0, 0, 0, 0.3); + --vscode-problemsErrorIcon-foreground: #e51400; + --vscode-problemsWarningIcon-foreground: #bf8803; + --vscode-problemsInfoIcon-foreground: #1a85ff; + --vscode-charts-foreground: #434343; + --vscode-charts-lines: rgba(67, 67, 67, 0.5); + --vscode-charts-red: #e51400; + --vscode-charts-blue: #1a85ff; + --vscode-charts-yellow: #bf8803; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #388a34; + --vscode-charts-purple: #652d90; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #434343; + --vscode-symbolIcon-booleanForeground: #434343; + --vscode-symbolIcon-classForeground: #d67e00; + --vscode-symbolIcon-colorForeground: #434343; + --vscode-symbolIcon-constantForeground: #434343; + --vscode-symbolIcon-constructorForeground: #652d90; + --vscode-symbolIcon-enumeratorForeground: #d67e00; + --vscode-symbolIcon-enumeratorMemberForeground: #007acc; + --vscode-symbolIcon-eventForeground: #d67e00; + --vscode-symbolIcon-fieldForeground: #007acc; + --vscode-symbolIcon-fileForeground: #434343; + --vscode-symbolIcon-folderForeground: #434343; + --vscode-symbolIcon-functionForeground: #652d90; + --vscode-symbolIcon-interfaceForeground: #007acc; + --vscode-symbolIcon-keyForeground: #434343; + --vscode-symbolIcon-keywordForeground: #434343; + --vscode-symbolIcon-methodForeground: #652d90; + --vscode-symbolIcon-moduleForeground: #434343; + --vscode-symbolIcon-namespaceForeground: #434343; + --vscode-symbolIcon-nullForeground: #434343; + --vscode-symbolIcon-numberForeground: #434343; + --vscode-symbolIcon-objectForeground: #434343; + --vscode-symbolIcon-operatorForeground: #434343; + --vscode-symbolIcon-packageForeground: #434343; + --vscode-symbolIcon-propertyForeground: #434343; + --vscode-symbolIcon-referenceForeground: #434343; + --vscode-symbolIcon-snippetForeground: #434343; + --vscode-symbolIcon-stringForeground: #434343; + --vscode-symbolIcon-structForeground: #434343; + --vscode-symbolIcon-textForeground: #434343; + --vscode-symbolIcon-typeParameterForeground: #434343; + --vscode-symbolIcon-unitForeground: #434343; + --vscode-symbolIcon-variableForeground: #007acc; + --vscode-actionBar-toggledBackground: #e7e7e7; + --vscode-editorHoverWidget-highlightForeground: #2e2e2e; + --vscode-editor-lineHighlightBackground: #f1f1f1; + --vscode-editor-lineHighlightBorder: #eeeeee; + --vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-editor-symbolHighlightBackground: rgba(255, 102, 0, 0.27); + --vscode-editorCursor-foreground: #000000; + --vscode-editorWhitespace-foreground: #ececec; + --vscode-editorLineNumber-foreground: #a8a8a8; + --vscode-editorIndentGuide-background: #e5e5e5; + --vscode-editorIndentGuide-activeBackground: #d7d8d7; + --vscode-editorIndentGuide-background1: #e5e5e5; + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: #d7d8d7; + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #0b216f; + --vscode-editorLineNumber-activeForeground: #585858; + --vscode-editorRuler-foreground: #d3d3d3; + --vscode-editorCodeLens-foreground: #919191; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #b9b9b9; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #ffffff; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47); + --vscode-editorGhostText-foreground: rgba(0, 0, 0, 0.47); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #bf8803; + --vscode-editorOverviewRuler-infoForeground: #1a85ff; + --vscode-editorBracketHighlight-foreground1: #0431fa; + --vscode-editorBracketHighlight-foreground2: #319331; + --vscode-editorBracketHighlight-foreground3: #7b3814; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #cea33d; + --vscode-editorUnicodeHighlight-background: rgba(206, 163, 61, 0.08); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(255, 102, 0, 0.08); + --vscode-editorGutter-foldingControlForeground: #424242; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25); + --vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.25); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: #f3f3f3; + --vscode-peekViewTitleLabel-foreground: #000000; + --vscode-peekViewTitleDescription-foreground: #616161; + --vscode-peekView-border: #1a85ff; + --vscode-peekViewResult-background: #f3f3f3; + --vscode-peekViewResult-lineForeground: #646465; + --vscode-peekViewResult-fileForeground: #1e1e1e; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #6c6c6c; + --vscode-peekViewEditor-background: #f2f8fc; + --vscode-peekViewEditorGutter-background: #f2f8fc; + --vscode-peekViewEditorStickyScroll-background: #f2f8fc; + --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3); + --vscode-peekViewEditor-matchHighlightBackground: rgba(245, 216, 2, 0.87); + --vscode-editorMarkerNavigationError-background: #e51400; + --vscode-editorMarkerNavigationError-headerBackground: rgba(229, 20, 0, 0.1); + --vscode-editorMarkerNavigationWarning-background: #bf8803; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(191, 136, 3, 0.1); + --vscode-editorMarkerNavigationInfo-background: #1a85ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(26, 133, 255, 0.1); + --vscode-editorMarkerNavigation-background: #ffffff; + --vscode-editorSuggestWidget-background: #f2f2f2; + --vscode-editorSuggestWidget-border: #c8c8c8; + --vscode-editorSuggestWidget-foreground: #000000; + --vscode-editorSuggestWidget-selectedForeground: #ff6600; + --vscode-editorSuggestWidget-selectedIconForeground: #ffffff; + --vscode-editorSuggestWidget-selectedBackground: #cedeef; + --vscode-editorSuggestWidget-highlightForeground: #2e2e2e; + --vscode-editorSuggestWidget-focusHighlightForeground: #2e2e2e; + --vscode-editorSuggestWidgetStatus-foreground: rgba(0, 0, 0, 0.5); + --vscode-tab-activeBackground: #ffffff; + --vscode-tab-unfocusedActiveBackground: #f7f7f6; + --vscode-tab-inactiveBackground: #ffffff; + --vscode-tab-unfocusedInactiveBackground: #e9e9e8; + --vscode-tab-activeForeground: #434343; + --vscode-tab-inactiveForeground: #686969; + --vscode-tab-unfocusedActiveForeground: #434343; + --vscode-tab-unfocusedInactiveForeground: #686969; + --vscode-tab-border: #dad9d9; + --vscode-tab-lastPinnedBorder: #a9a9a9; + --vscode-tab-activeBorder: #e1e2e2; + --vscode-tab-unfocusedActiveBorder: #e1e2e2; + --vscode-tab-activeBorderTop: #e2e2e2; + --vscode-tab-unfocusedActiveBorderTop: rgba(226, 226, 226, 0.7); + --vscode-tab-activeModifiedBorder: #33aaee; + --vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, 0.7); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, 0.25); + --vscode-editorPane-background: #ffffff; + --vscode-editorGroup-emptyBackground: #f7f7f6; + --vscode-editorGroupHeader-tabsBackground: #ffffff; + --vscode-editorGroupHeader-tabsBorder: #ffffff; + --vscode-editorGroupHeader-noTabsBackground: #e9e9e8; + --vscode-editorGroupHeader-border: #ffffff; + --vscode-editorGroup-border: #e1e2e2; + --vscode-editorGroup-dropBackground: rgba(255, 102, 0, 0.27); + --vscode-editorGroup-dropIntoPromptForeground: #434343; + --vscode-editorGroup-dropIntoPromptBackground: #f2f2f2; + --vscode-sideBySideEditor-horizontalBorder: #e1e2e2; + --vscode-sideBySideEditor-verticalBorder: #e1e2e2; + --vscode-panel-background: #ffffff; + --vscode-panel-border: rgba(128, 128, 128, 0.35); + --vscode-panelTitle-activeForeground: #ff6600; + --vscode-panelTitle-inactiveForeground: rgba(255, 102, 0, 0.75); + --vscode-panelTitle-activeBorder: #000000; + --vscode-panelInput-border: #dddddd; + --vscode-panel-dropBorder: #ff6600; + --vscode-panelSection-dropBackground: rgba(255, 102, 0, 0.27); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: rgba(128, 128, 128, 0.35); + --vscode-banner-background: #699ace; + --vscode-banner-foreground: #ff6600; + --vscode-banner-iconForeground: #1a85ff; + --vscode-statusBar-foreground: #3e0001; + --vscode-statusBar-noFolderForeground: #3e0001; + --vscode-statusBar-background: rgba(255, 102, 0, 0.2); + --vscode-statusBar-noFolderBackground: #ededed; + --vscode-statusBar-border: rgba(255, 102, 0, 0.43); + --vscode-statusBar-focusBorder: #3e0001; + --vscode-statusBar-noFolderBorder: rgba(255, 102, 0, 0.43); + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #3e0001; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #3e0001; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #3e0001; + --vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5); + --vscode-statusBarItem-prominentHoverForeground: #3e0001; + --vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.3); + --vscode-statusBarItem-errorBackground: #990000; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #3e0001; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #725102; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #3e0001; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #ffffff; + --vscode-activityBar-foreground: #ff6600; + --vscode-activityBar-inactiveForeground: #686969; + --vscode-activityBar-border: #ffffff; + --vscode-activityBar-activeBorder: #000000; + --vscode-activityBar-activeBackground: #ffffff; + --vscode-activityBar-dropBorder: #ff6600; + --vscode-activityBarBadge-background: #ff6600; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-profileBadge-background: #c4c4c4; + --vscode-profileBadge-foreground: #333333; + --vscode-statusBarItem-remoteBackground: #ff6600; + --vscode-statusBarItem-remoteForeground: #ffffff; + --vscode-statusBarItem-remoteHoverForeground: #3e0001; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #ffffff; + --vscode-statusBarItem-offlineHoverForeground: #3e0001; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #ff6600; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #ffffff; + --vscode-sideBar-foreground: #000000; + --vscode-sideBar-border: rgba(255, 102, 0, 0.28); + --vscode-sideBarTitle-foreground: #000000; + --vscode-sideBar-dropBackground: #f9f5f5; + --vscode-sideBarSectionHeader-background: rgba(255, 102, 0, 0.2); + --vscode-sideBarSectionHeader-foreground: #3e0001; + --vscode-sideBarSectionHeader-border: #ff6600; + --vscode-titleBar-activeForeground: #3e0001; + --vscode-titleBar-inactiveForeground: #acacab; + --vscode-titleBar-activeBackground: rgba(255, 102, 0, 0.2); + --vscode-titleBar-inactiveBackground: #f7f7f6; + --vscode-titleBar-border: #ffffff; + --vscode-menubar-selectionForeground: #ffffff; + --vscode-menubar-selectionBackground: #ff6600; + --vscode-notifications-foreground: #434343; + --vscode-notifications-background: #f2f2f2; + --vscode-notificationLink-foreground: #006ab1; + --vscode-notificationCenterHeader-background: #e6e6e6; + --vscode-notifications-border: #e6e6e6; + --vscode-notificationsErrorIcon-foreground: #e51400; + --vscode-notificationsWarningIcon-foreground: #bf8803; + --vscode-notificationsInfoIcon-foreground: #1a85ff; + --vscode-commandCenter-foreground: #3e0001; + --vscode-commandCenter-activeForeground: #ffffff; + --vscode-commandCenter-inactiveForeground: #acacab; + --vscode-commandCenter-background: rgba(0, 0, 0, 0.05); + --vscode-commandCenter-activeBackground: rgba(0, 0, 0, 0.08); + --vscode-commandCenter-border: rgba(62, 0, 1, 0.2); + --vscode-commandCenter-activeBorder: rgba(62, 0, 1, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(172, 172, 171, 0.25); + --vscode-chat-requestBorder: rgba(0, 0, 0, 0.1); + --vscode-chat-slashCommandBackground: #ff6600; + --vscode-chat-slashCommandForeground: #ffffff; + --vscode-simpleFindWidget-sashBorder: #c8c8c8; + --vscode-commentsView-resolvedIcon: rgba(97, 97, 97, 0.5); + --vscode-commentsView-unresolvedIcon: #ffffff; + --vscode-editorCommentsWidget-resolvedBorder: rgba(97, 97, 97, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #ffffff; + --vscode-editorCommentsWidget-rangeBackground: rgba(255, 255, 255, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(255, 255, 255, 0.1); + --vscode-editorGutter-commentRangeForeground: #d0d0d0; + --vscode-editorOverviewRuler-commentForeground: #d0d0d0; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #d0d0d0; + --vscode-editorGutter-commentGlyphForeground: #000000; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #000000; + --vscode-debugToolBar-background: #f3f3f3; + --vscode-debugIcon-startForeground: #388a34; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(206, 231, 206, 0.45); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.4); + --vscode-mergeEditor-changeBase\.background: #ffcccc; + --vscode-mergeEditor-changeBase\.word\.background: #ffa3a3; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #444444; + --vscode-settings-settingsHeaderHoverForeground: rgba(68, 68, 68, 0.7); + --vscode-settings-modifiedItemIndicator: #66afe0; + --vscode-settings-headerBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-sashBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-dropdownBackground: #ffffff; + --vscode-settings-dropdownForeground: #222222; + --vscode-settings-dropdownBorder: #bfbfbf; + --vscode-settings-dropdownListBorder: #c8c8c8; + --vscode-settings-checkboxBackground: #ffffff; + --vscode-settings-checkboxForeground: #222222; + --vscode-settings-checkboxBorder: #bfbfbf; + --vscode-settings-textInputBackground: #ffffff; + --vscode-settings-textInputForeground: #222222; + --vscode-settings-textInputBorder: #bfbfbf; + --vscode-settings-numberInputBackground: #ffffff; + --vscode-settings-numberInputForeground: #222222; + --vscode-settings-numberInputBorder: #bfbfbf; + --vscode-settings-focusedRowBackground: rgba(240, 240, 240, 0.6); + --vscode-settings-rowHoverBackground: rgba(240, 240, 240, 0.3); + --vscode-settings-focusedRowBorder: #ffffff; + --vscode-terminal-foreground: #333333; + --vscode-terminal-selectionBackground: rgba(255, 102, 0, 0.27); + --vscode-terminal-inactiveSelectionBackground: rgba(255, 102, 0, 0.13); + --vscode-terminalCommandDecoration-defaultBackground: rgba(0, 0, 0, 0.25); + --vscode-terminalCommandDecoration-successBackground: #2090d3; + --vscode-terminalCommandDecoration-errorBackground: #e51400; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: rgba(128, 128, 128, 0.35); + --vscode-terminal-findMatchBackground: #a8ac94; + --vscode-terminal-hoverHighlightBackground: rgba(173, 214, 255, 0.07); + --vscode-terminal-findMatchHighlightBackground: rgba(255, 102, 0, 0.27); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(255, 102, 0, 0.27); + --vscode-terminal-tab\.activeBorder: #e1e2e2; + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #e51400; + --vscode-testing-peekHeaderBackground: rgba(229, 20, 0, 0.1); + --vscode-testing-message\.error\.decorationForeground: #e51400; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(0, 0, 0, 0.5); + --vscode-welcomePage-background: #ffffff; + --vscode-welcomePage-tileBackground: #f2f2f2; + --vscode-welcomePage-tileHoverBackground: #dadada; + --vscode-welcomePage-tileBorder: rgba(0, 0, 0, 0.1); + --vscode-welcomePage-progress\.background: #ffffff; + --vscode-welcomePage-progress\.foreground: #006ab1; + --vscode-walkthrough-stepTitle\.foreground: #000000; + --vscode-walkThrough-embeddedEditorBackground: #f4f4f4; + --vscode-inlineChat-background: #f2f2f2; + --vscode-inlineChat-border: #c8c8c8; + --vscode-inlineChat-shadow: #dddddd; + --vscode-inlineChat-regionHighlight: rgba(173, 214, 255, 0.15); + --vscode-inlineChatInput-border: #c8c8c8; + --vscode-inlineChatInput-focusBorder: #ffffff; + --vscode-inlineChatInput-placeholderForeground: rgba(67, 67, 67, 0.5); + --vscode-inlineChatInput-background: #ffffff; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.13); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-debugExceptionWidget-border: #a31515; + --vscode-debugExceptionWidget-background: #f1dfde; + --vscode-ports-iconRunningProcessForeground: #ff6600; + --vscode-statusBar-debuggingBackground: #ededed; + --vscode-statusBar-debuggingForeground: #3e0001; + --vscode-statusBar-debuggingBorder: rgba(255, 102, 0, 0.43); + --vscode-editor-inlineValuesForeground: rgba(0, 0, 0, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: #2090d3; + --vscode-editorGutter-addedBackground: #48985d; + --vscode-editorGutter-deletedBackground: #e51400; + --vscode-minimapGutter-modifiedBackground: #2090d3; + --vscode-minimapGutter-addedBackground: #48985d; + --vscode-minimapGutter-deletedBackground: #e51400; + --vscode-editorOverviewRuler-modifiedForeground: rgba(32, 144, 211, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 152, 93, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(229, 20, 0, 0.6); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #be8700; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: #dbdbdb; + --vscode-notebook-focusedEditorBorder: #ffffff; + --vscode-notebookStatusSuccessIcon-foreground: #388a34; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #388a34; + --vscode-notebookStatusErrorIcon-foreground: #ff0000; + --vscode-notebookStatusRunningIcon-foreground: #434343; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #dbdbdb; + --vscode-notebook-selectedCellBorder: #dbdbdb; + --vscode-notebook-focusedCellBorder: #ffffff; + --vscode-notebook-inactiveFocusedCellBorder: #dbdbdb; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(0, 0, 0, 0.08); + --vscode-notebook-cellInsertionIndicator: #ffffff; + --vscode-notebookScrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-notebook-symbolHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-notebook-cellEditorBackground: #ffffff; + --vscode-notebook-editorBackground: #ffffff; + --vscode-keybindingTable-headerBackground: rgba(67, 67, 67, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(67, 67, 67, 0.04); + --vscode-searchEditor-textInputBorder: #bfbfbf; + --vscode-debugTokenExpression-name: #9b46b0; + --vscode-debugTokenExpression-value: rgba(108, 108, 108, 0.8); + --vscode-debugTokenExpression-string: #a31515; + --vscode-debugTokenExpression-boolean: #0000ff; + --vscode-debugTokenExpression-number: #098658; + --vscode-debugTokenExpression-error: #e51400; + --vscode-debugView-exceptionLabelForeground: #ffffff; + --vscode-debugView-exceptionLabelBackground: #a31515; + --vscode-debugView-stateLabelForeground: #434343; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #1a85ff; + --vscode-debugConsole-warningForeground: #bf8803; + --vscode-debugConsole-errorForeground: #ff0000; + --vscode-debugConsole-sourceForeground: #434343; + --vscode-debugConsoleInputIcon-foreground: #434343; + --vscode-debugIcon-pauseForeground: #007acc; + --vscode-debugIcon-stopForeground: #a1260d; + --vscode-debugIcon-disconnectForeground: #a1260d; + --vscode-debugIcon-restartForeground: #388a34; + --vscode-debugIcon-stepOverForeground: #007acc; + --vscode-debugIcon-stepIntoForeground: #007acc; + --vscode-debugIcon-stepOutForeground: #007acc; + --vscode-debugIcon-continueForeground: #007acc; + --vscode-debugIcon-stepBackForeground: #007acc; + --vscode-scm-providerBorder: #c8c8c8; + --vscode-extensionButton-background: #ff6600; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #ff6600; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #ff6600; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: #ff6600; + --vscode-extensionIcon-starForeground: #df6100; + --vscode-extensionIcon-verifiedForeground: #006ab1; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #b51e78; + --vscode-terminal-ansiBlack: #000000; + --vscode-terminal-ansiRed: #cd3131; + --vscode-terminal-ansiGreen: #00bc00; + --vscode-terminal-ansiYellow: #949800; + --vscode-terminal-ansiBlue: #0451a5; + --vscode-terminal-ansiMagenta: #bc05bc; + --vscode-terminal-ansiCyan: #0598bc; + --vscode-terminal-ansiWhite: #555555; + --vscode-terminal-ansiBrightBlack: #666666; + --vscode-terminal-ansiBrightRed: #cd3131; + --vscode-terminal-ansiBrightGreen: #14ce14; + --vscode-terminal-ansiBrightYellow: #b5ba00; + --vscode-terminal-ansiBrightBlue: #0451a5; + --vscode-terminal-ansiBrightMagenta: #bc05bc; + --vscode-terminal-ansiBrightCyan: #0598bc; + --vscode-terminal-ansiBrightWhite: #a5a5a5; + --vscode-interactive-activeCodeBorder: #1a85ff; + --vscode-interactive-inactiveCodeBorder: #dbdbdb; + --vscode-gitDecoration-addedResourceForeground: #587c0c; + --vscode-gitDecoration-modifiedResourceForeground: #895503; + --vscode-gitDecoration-deletedResourceForeground: #ad0707; + --vscode-gitDecoration-renamedResourceForeground: #007100; + --vscode-gitDecoration-untrackedResourceForeground: #007100; + --vscode-gitDecoration-ignoredResourceForeground: #8e8e90; + --vscode-gitDecoration-stageModifiedResourceForeground: #895503; + --vscode-gitDecoration-stageDeletedResourceForeground: #ad0707; + --vscode-gitDecoration-conflictingResourceForeground: #ad0707; + --vscode-gitDecoration-submoduleResourceForeground: #1258a7; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/light-quiet.scss b/vendor/mynah-ui/example/src/styles/themes/light-quiet.scss new file mode 100644 index 00000000..123dd638 --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/light-quiet.scss @@ -0,0 +1,605 @@ +html[theme='light-quiet']:root { + --vscode-foreground: #616161; + --vscode-disabledForeground: rgba(97, 97, 97, 0.5); + --vscode-errorForeground: #f1897f; + --vscode-descriptionForeground: #717171; + --vscode-icon-foreground: #424242; + --vscode-focusBorder: #9769dc; + --vscode-selection-background: #c9d0d9; + --vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18); + --vscode-textLink-foreground: #006ab1; + --vscode-textLink-activeForeground: #006ab1; + --vscode-textPreformat-foreground: #a31515; + --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1); + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(220, 220, 220, 0.4); + --vscode-widget-shadow: rgba(0, 0, 0, 0.16); + --vscode-input-background: #ffffff; + --vscode-input-foreground: #616161; + --vscode-inputOption-activeBorder: #adafb7; + --vscode-inputOption-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-inputOption-activeBackground: rgba(151, 105, 220, 0.2); + --vscode-inputOption-activeForeground: #000000; + --vscode-input-placeholderForeground: rgba(97, 97, 97, 0.5); + --vscode-inputValidation-infoBackground: #f2fcff; + --vscode-inputValidation-infoBorder: #4ec1e5; + --vscode-inputValidation-warningBackground: #fffee2; + --vscode-inputValidation-warningBorder: #ffe055; + --vscode-inputValidation-errorBackground: #ffeaea; + --vscode-inputValidation-errorBorder: #f1897f; + --vscode-dropdown-background: #f5f5f5; + --vscode-dropdown-foreground: #616161; + --vscode-dropdown-border: #cecece; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #705697; + --vscode-button-hoverBackground: #5a4579; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #5f6a79; + --vscode-button-secondaryHoverBackground: #4c5561; + --vscode-badge-background: rgba(112, 86, 151, 0.67); + --vscode-badge-foreground: #333333; + --vscode-scrollbar-shadow: #dddddd; + --vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-progressBar-background: #705697; + --vscode-editorError-foreground: #e51400; + --vscode-editorWarning-foreground: #bf8803; + --vscode-editorInfo-foreground: #1a85ff; + --vscode-editorHint-foreground: #6c6c6c; + --vscode-sash-hoverBorder: #9769dc; + --vscode-editor-background: #f5f5f5; + --vscode-editor-foreground: #333333; + --vscode-editorStickyScroll-background: #f5f5f5; + --vscode-editorStickyScrollHover-background: #f0f0f0; + --vscode-editorWidget-background: #f3f3f3; + --vscode-editorWidget-foreground: #616161; + --vscode-editorWidget-border: #c8c8c8; + --vscode-quickInput-background: #f3f3f3; + --vscode-quickInput-foreground: #616161; + --vscode-quickInputTitle-background: rgba(0, 0, 0, 0.06); + --vscode-pickerGroup-foreground: #a6b39b; + --vscode-pickerGroup-border: #749351; + --vscode-keybindingLabel-background: rgba(221, 221, 221, 0.4); + --vscode-keybindingLabel-foreground: #555555; + --vscode-keybindingLabel-border: rgba(204, 204, 204, 0.4); + --vscode-keybindingLabel-bottomBorder: rgba(187, 187, 187, 0.4); + --vscode-editor-selectionBackground: #c9d0d9; + --vscode-editor-inactiveSelectionBackground: rgba(201, 208, 217, 0.5); + --vscode-editor-selectionHighlightBackground: rgba(224, 228, 234, 0.6); + --vscode-editor-findMatchBackground: #bf9cac; + --vscode-editor-findMatchHighlightBackground: rgba(237, 201, 216, 0.6); + --vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3); + --vscode-searchEditor-findMatchBackground: rgba(237, 201, 216, 0.4); + --vscode-search-resultsInfoForeground: #616161; + --vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15); + --vscode-editorHoverWidget-background: #f3f3f3; + --vscode-editorHoverWidget-foreground: #616161; + --vscode-editorHoverWidget-border: #c8c8c8; + --vscode-editorHoverWidget-statusBarBackground: #e7e7e7; + --vscode-editorLink-activeForeground: #0000ff; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(112, 86, 151, 0.07); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(112, 86, 151, 0.07); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(112, 86, 151, 0.07); + --vscode-editorLightBulb-foreground: #ddb100; + --vscode-editorLightBulbAutoFix-foreground: #007acc; + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.25); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(34, 34, 34, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #e4e4e4; + --vscode-diffEditor-unchangedRegionForeground: #4d4c4c; + --vscode-diffEditor-unchangedCodeBackground: rgba(184, 184, 184, 0.16); + --vscode-list-focusOutline: #9769dc; + --vscode-list-activeSelectionBackground: #c4d9b1; + --vscode-list-activeSelectionForeground: #6c6c6c; + --vscode-list-inactiveSelectionBackground: #d3dbcd; + --vscode-list-hoverBackground: #e0e0e0; + --vscode-list-dropBackground: #d6ebff; + --vscode-list-highlightForeground: #9769dc; + --vscode-list-focusHighlightForeground: #9769dc; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #b01011; + --vscode-list-warningForeground: #855f00; + --vscode-listFilterWidget-background: #f3f3f3; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.16); + --vscode-list-filterMatchBackground: rgba(237, 201, 216, 0.6); + --vscode-tree-indentGuidesStroke: #a9a9a9; + --vscode-tree-inactiveIndentGuidesStroke: rgba(169, 169, 169, 0.4); + --vscode-tree-tableColumnsBorder: rgba(97, 97, 97, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(97, 97, 97, 0.04); + --vscode-list-deemphasizedForeground: #8e8e90; + --vscode-checkbox-background: #f5f5f5; + --vscode-checkbox-selectBackground: #f3f3f3; + --vscode-checkbox-foreground: #616161; + --vscode-checkbox-border: #cecece; + --vscode-checkbox-selectBorder: #424242; + --vscode-quickInputList-focusForeground: #6c6c6c; + --vscode-quickInputList-focusBackground: #cadeb9; + --vscode-menu-foreground: #616161; + --vscode-menu-background: #f5f5f5; + --vscode-menu-selectionForeground: #6c6c6c; + --vscode-menu-selectionBackground: #c4d9b1; + --vscode-menu-separatorBackground: #d4d4d4; + --vscode-toolbar-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-toolbar-activeBackground: rgba(166, 166, 166, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2); + --vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5); + --vscode-breadcrumb-foreground: rgba(97, 97, 97, 0.8); + --vscode-breadcrumb-background: #f5f5f5; + --vscode-breadcrumb-focusForeground: #4e4e4e; + --vscode-breadcrumb-activeSelectionForeground: #4e4e4e; + --vscode-breadcrumbPicker-background: #f3f3f3; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #c9c9c9; + --vscode-minimap-selectionHighlight: #c9d0d9; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #bf8803; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(100, 100, 100, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(0, 0, 0, 0.3); + --vscode-problemsErrorIcon-foreground: #e51400; + --vscode-problemsWarningIcon-foreground: #bf8803; + --vscode-problemsInfoIcon-foreground: #1a85ff; + --vscode-charts-foreground: #616161; + --vscode-charts-lines: rgba(97, 97, 97, 0.5); + --vscode-charts-red: #e51400; + --vscode-charts-blue: #1a85ff; + --vscode-charts-yellow: #bf8803; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #388a34; + --vscode-charts-purple: #652d90; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #616161; + --vscode-symbolIcon-booleanForeground: #616161; + --vscode-symbolIcon-classForeground: #d67e00; + --vscode-symbolIcon-colorForeground: #616161; + --vscode-symbolIcon-constantForeground: #616161; + --vscode-symbolIcon-constructorForeground: #652d90; + --vscode-symbolIcon-enumeratorForeground: #d67e00; + --vscode-symbolIcon-enumeratorMemberForeground: #007acc; + --vscode-symbolIcon-eventForeground: #d67e00; + --vscode-symbolIcon-fieldForeground: #007acc; + --vscode-symbolIcon-fileForeground: #616161; + --vscode-symbolIcon-folderForeground: #616161; + --vscode-symbolIcon-functionForeground: #652d90; + --vscode-symbolIcon-interfaceForeground: #007acc; + --vscode-symbolIcon-keyForeground: #616161; + --vscode-symbolIcon-keywordForeground: #616161; + --vscode-symbolIcon-methodForeground: #652d90; + --vscode-symbolIcon-moduleForeground: #616161; + --vscode-symbolIcon-namespaceForeground: #616161; + --vscode-symbolIcon-nullForeground: #616161; + --vscode-symbolIcon-numberForeground: #616161; + --vscode-symbolIcon-objectForeground: #616161; + --vscode-symbolIcon-operatorForeground: #616161; + --vscode-symbolIcon-packageForeground: #616161; + --vscode-symbolIcon-propertyForeground: #616161; + --vscode-symbolIcon-referenceForeground: #616161; + --vscode-symbolIcon-snippetForeground: #616161; + --vscode-symbolIcon-stringForeground: #616161; + --vscode-symbolIcon-structForeground: #616161; + --vscode-symbolIcon-textForeground: #616161; + --vscode-symbolIcon-typeParameterForeground: #616161; + --vscode-symbolIcon-unitForeground: #616161; + --vscode-symbolIcon-variableForeground: #007acc; + --vscode-actionBar-toggledBackground: rgba(151, 105, 220, 0.2); + --vscode-editorHoverWidget-highlightForeground: #9769dc; + --vscode-editor-lineHighlightBackground: #e4f6d4; + --vscode-editor-lineHighlightBorder: #eeeeee; + --vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-editor-symbolHighlightBackground: rgba(237, 201, 216, 0.6); + --vscode-editorCursor-foreground: #54494b; + --vscode-editorWhitespace-foreground: #aaaaaa; + --vscode-editorLineNumber-foreground: #6d705b; + --vscode-editorIndentGuide-background: rgba(170, 170, 170, 0.38); + --vscode-editorIndentGuide-activeBackground: rgba(119, 119, 119, 0.69); + --vscode-editorIndentGuide-background1: rgba(170, 170, 170, 0.38); + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: rgba(119, 119, 119, 0.69); + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #0b216f; + --vscode-editorLineNumber-activeForeground: #9769dc; + --vscode-editorRuler-foreground: #d3d3d3; + --vscode-editorCodeLens-foreground: #919191; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #b9b9b9; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #f5f5f5; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47); + --vscode-editorGhostText-foreground: rgba(0, 0, 0, 0.47); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #bf8803; + --vscode-editorOverviewRuler-infoForeground: #1a85ff; + --vscode-editorBracketHighlight-foreground1: #0431fa; + --vscode-editorBracketHighlight-foreground2: #319331; + --vscode-editorBracketHighlight-foreground3: #7b3814; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #cea33d; + --vscode-editorUnicodeHighlight-background: rgba(206, 163, 61, 0.08); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(201, 208, 217, 0.3); + --vscode-editorGutter-foldingControlForeground: #424242; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25); + --vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.25); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: #f2f8fc; + --vscode-peekViewTitleLabel-foreground: #000000; + --vscode-peekViewTitleDescription-foreground: #616161; + --vscode-peekView-border: #705697; + --vscode-peekViewResult-background: #f2f8fc; + --vscode-peekViewResult-lineForeground: #646465; + --vscode-peekViewResult-fileForeground: #1e1e1e; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #6c6c6c; + --vscode-peekViewEditor-background: #f2f8fc; + --vscode-peekViewEditorGutter-background: #f2f8fc; + --vscode-peekViewEditorStickyScroll-background: #f2f8fc; + --vscode-peekViewResult-matchHighlightBackground: #93c6d6; + --vscode-peekViewEditor-matchHighlightBackground: #c2dfe3; + --vscode-editorMarkerNavigationError-background: #e51400; + --vscode-editorMarkerNavigationError-headerBackground: rgba(229, 20, 0, 0.1); + --vscode-editorMarkerNavigationWarning-background: #bf8803; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(191, 136, 3, 0.1); + --vscode-editorMarkerNavigationInfo-background: #1a85ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(26, 133, 255, 0.1); + --vscode-editorMarkerNavigation-background: #f5f5f5; + --vscode-editorSuggestWidget-background: #f3f3f3; + --vscode-editorSuggestWidget-border: #c8c8c8; + --vscode-editorSuggestWidget-foreground: #333333; + --vscode-editorSuggestWidget-selectedForeground: #6c6c6c; + --vscode-editorSuggestWidget-selectedBackground: #cadeb9; + --vscode-editorSuggestWidget-highlightForeground: #9769dc; + --vscode-editorSuggestWidget-focusHighlightForeground: #9769dc; + --vscode-editorSuggestWidgetStatus-foreground: rgba(51, 51, 51, 0.5); + --vscode-tab-activeBackground: #f5f5f5; + --vscode-tab-unfocusedActiveBackground: #f5f5f5; + --vscode-tab-inactiveBackground: #ececec; + --vscode-tab-unfocusedInactiveBackground: #ececec; + --vscode-tab-activeForeground: #333333; + --vscode-tab-inactiveForeground: rgba(51, 51, 51, 0.7); + --vscode-tab-unfocusedActiveForeground: rgba(51, 51, 51, 0.7); + --vscode-tab-unfocusedInactiveForeground: rgba(51, 51, 51, 0.35); + --vscode-tab-border: #f3f3f3; + --vscode-tab-lastPinnedBorder: #c9d0d9; + --vscode-tab-activeModifiedBorder: #33aaee; + --vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, 0.7); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, 0.25); + --vscode-editorPane-background: #f5f5f5; + --vscode-editorGroupHeader-tabsBackground: #f3f3f3; + --vscode-editorGroupHeader-noTabsBackground: #f5f5f5; + --vscode-editorGroup-border: #e7e7e7; + --vscode-editorGroup-dropBackground: rgba(201, 208, 217, 0.53); + --vscode-editorGroup-dropIntoPromptForeground: #616161; + --vscode-editorGroup-dropIntoPromptBackground: #f3f3f3; + --vscode-sideBySideEditor-horizontalBorder: #e7e7e7; + --vscode-sideBySideEditor-verticalBorder: #e7e7e7; + --vscode-panel-background: #f5f5f5; + --vscode-panel-border: rgba(128, 128, 128, 0.35); + --vscode-panelTitle-activeForeground: #424242; + --vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, 0.75); + --vscode-panelTitle-activeBorder: #424242; + --vscode-panelInput-border: #dddddd; + --vscode-panel-dropBorder: #424242; + --vscode-panelSection-dropBackground: rgba(201, 208, 217, 0.53); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: rgba(128, 128, 128, 0.35); + --vscode-banner-background: #89b262; + --vscode-banner-foreground: #6c6c6c; + --vscode-banner-iconForeground: #1a85ff; + --vscode-statusBar-foreground: #ffffff; + --vscode-statusBar-noFolderForeground: #ffffff; + --vscode-statusBar-background: #705697; + --vscode-statusBar-noFolderBackground: #705697; + --vscode-statusBar-focusBorder: #ffffff; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #ffffff; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #ffffff; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #ffffff; + --vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5); + --vscode-statusBarItem-prominentHoverForeground: #ffffff; + --vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.3); + --vscode-statusBarItem-errorBackground: #c72516; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #ffffff; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #725102; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #ffffff; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #ededf5; + --vscode-activityBar-foreground: #705697; + --vscode-activityBar-inactiveForeground: rgba(112, 86, 151, 0.4); + --vscode-activityBar-activeBorder: #705697; + --vscode-activityBar-dropBorder: #705697; + --vscode-activityBarBadge-background: #705697; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-profileBadge-background: #c4c4c4; + --vscode-profileBadge-foreground: #333333; + --vscode-statusBarItem-remoteBackground: #4e3c69; + --vscode-statusBarItem-remoteForeground: #ffffff; + --vscode-statusBarItem-remoteHoverForeground: #ffffff; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #ffffff; + --vscode-statusBarItem-offlineHoverForeground: #ffffff; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #705697; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #f2f2f2; + --vscode-sideBar-dropBackground: rgba(201, 208, 217, 0.53); + --vscode-sideBarSectionHeader-background: #ede8ef; + --vscode-titleBar-activeForeground: #333333; + --vscode-titleBar-inactiveForeground: rgba(51, 51, 51, 0.6); + --vscode-titleBar-activeBackground: #c4b7d7; + --vscode-titleBar-inactiveBackground: rgba(196, 183, 215, 0.6); + --vscode-menubar-selectionForeground: #333333; + --vscode-menubar-selectionBackground: rgba(184, 184, 184, 0.31); + --vscode-notifications-foreground: #616161; + --vscode-notifications-background: #f3f3f3; + --vscode-notificationLink-foreground: #006ab1; + --vscode-notificationCenterHeader-background: #e7e7e7; + --vscode-notifications-border: #e7e7e7; + --vscode-notificationsErrorIcon-foreground: #e51400; + --vscode-notificationsWarningIcon-foreground: #bf8803; + --vscode-notificationsInfoIcon-foreground: #1a85ff; + --vscode-commandCenter-foreground: #333333; + --vscode-commandCenter-activeForeground: #333333; + --vscode-commandCenter-inactiveForeground: rgba(51, 51, 51, 0.6); + --vscode-commandCenter-background: rgba(0, 0, 0, 0.05); + --vscode-commandCenter-activeBackground: rgba(0, 0, 0, 0.08); + --vscode-commandCenter-border: rgba(51, 51, 51, 0.2); + --vscode-commandCenter-activeBorder: rgba(51, 51, 51, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(51, 51, 51, 0.15); + --vscode-chat-requestBorder: rgba(0, 0, 0, 0.1); + --vscode-chat-slashCommandBackground: rgba(112, 86, 151, 0.67); + --vscode-chat-slashCommandForeground: #333333; + --vscode-simpleFindWidget-sashBorder: #c8c8c8; + --vscode-commentsView-resolvedIcon: rgba(97, 97, 97, 0.5); + --vscode-commentsView-unresolvedIcon: #9769dc; + --vscode-editorCommentsWidget-resolvedBorder: rgba(97, 97, 97, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #9769dc; + --vscode-editorCommentsWidget-rangeBackground: rgba(151, 105, 220, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(151, 105, 220, 0.1); + --vscode-editorGutter-commentRangeForeground: #c8d2c0; + --vscode-editorOverviewRuler-commentForeground: #c8d2c0; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #c8d2c0; + --vscode-editorGutter-commentGlyphForeground: #333333; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #333333; + --vscode-debugToolBar-background: #f3f3f3; + --vscode-debugIcon-startForeground: #388a34; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(206, 231, 206, 0.45); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.4); + --vscode-mergeEditor-changeBase\.background: #ffcccc; + --vscode-mergeEditor-changeBase\.word\.background: #ffa3a3; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #444444; + --vscode-settings-settingsHeaderHoverForeground: rgba(68, 68, 68, 0.7); + --vscode-settings-modifiedItemIndicator: #66afe0; + --vscode-settings-headerBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-sashBorder: rgba(128, 128, 128, 0.35); + --vscode-settings-dropdownBackground: #f5f5f5; + --vscode-settings-dropdownForeground: #616161; + --vscode-settings-dropdownBorder: #cecece; + --vscode-settings-dropdownListBorder: #c8c8c8; + --vscode-settings-checkboxBackground: #f5f5f5; + --vscode-settings-checkboxForeground: #616161; + --vscode-settings-checkboxBorder: #cecece; + --vscode-settings-textInputBackground: #ffffff; + --vscode-settings-textInputForeground: #616161; + --vscode-settings-numberInputBackground: #ffffff; + --vscode-settings-numberInputForeground: #616161; + --vscode-settings-focusedRowBackground: rgba(224, 224, 224, 0.6); + --vscode-settings-rowHoverBackground: rgba(224, 224, 224, 0.3); + --vscode-settings-focusedRowBorder: #9769dc; + --vscode-terminal-foreground: #333333; + --vscode-terminal-selectionBackground: #c9d0d9; + --vscode-terminal-inactiveSelectionBackground: rgba(201, 208, 217, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(0, 0, 0, 0.25); + --vscode-terminalCommandDecoration-successBackground: #2090d3; + --vscode-terminalCommandDecoration-errorBackground: #e51400; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: rgba(128, 128, 128, 0.35); + --vscode-terminal-findMatchBackground: #bf9cac; + --vscode-terminal-hoverHighlightBackground: rgba(173, 214, 255, 0.07); + --vscode-terminal-findMatchHighlightBackground: rgba(237, 201, 216, 0.6); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(201, 208, 217, 0.53); + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #e51400; + --vscode-testing-peekHeaderBackground: rgba(229, 20, 0, 0.1); + --vscode-testing-message\.error\.decorationForeground: #e51400; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(51, 51, 51, 0.5); + --vscode-welcomePage-tileBackground: #f0f0f7; + --vscode-welcomePage-tileHoverBackground: #dbdbdb; + --vscode-welcomePage-tileBorder: rgba(0, 0, 0, 0.1); + --vscode-welcomePage-progress\.background: #ffffff; + --vscode-welcomePage-progress\.foreground: #006ab1; + --vscode-walkthrough-stepTitle\.foreground: #000000; + --vscode-walkThrough-embeddedEditorBackground: rgba(0, 0, 0, 0.08); + --vscode-inlineChat-background: #f3f3f3; + --vscode-inlineChat-border: #c8c8c8; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.16); + --vscode-inlineChat-regionHighlight: rgba(173, 214, 255, 0.15); + --vscode-inlineChatInput-border: #c8c8c8; + --vscode-inlineChatInput-focusBorder: #9769dc; + --vscode-inlineChatInput-placeholderForeground: rgba(97, 97, 97, 0.5); + --vscode-inlineChatInput-background: #ffffff; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.13); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-debugExceptionWidget-border: #a31515; + --vscode-debugExceptionWidget-background: #f1dfde; + --vscode-ports-iconRunningProcessForeground: #749351; + --vscode-statusBar-debuggingBackground: #705697; + --vscode-statusBar-debuggingForeground: #ffffff; + --vscode-editor-inlineValuesForeground: rgba(0, 0, 0, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: #2090d3; + --vscode-editorGutter-addedBackground: #48985d; + --vscode-editorGutter-deletedBackground: #e51400; + --vscode-minimapGutter-modifiedBackground: #2090d3; + --vscode-minimapGutter-addedBackground: #48985d; + --vscode-minimapGutter-deletedBackground: #e51400; + --vscode-editorOverviewRuler-modifiedForeground: rgba(32, 144, 211, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 152, 93, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(229, 20, 0, 0.6); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #be8700; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: #d3dbcd; + --vscode-notebook-focusedEditorBorder: #9769dc; + --vscode-notebookStatusSuccessIcon-foreground: #388a34; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #388a34; + --vscode-notebookStatusErrorIcon-foreground: #f1897f; + --vscode-notebookStatusRunningIcon-foreground: #616161; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #d3dbcd; + --vscode-notebook-selectedCellBorder: #d3dbcd; + --vscode-notebook-focusedCellBorder: #9769dc; + --vscode-notebook-inactiveFocusedCellBorder: #d3dbcd; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(0, 0, 0, 0.08); + --vscode-notebook-cellInsertionIndicator: #9769dc; + --vscode-notebookScrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-notebook-symbolHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-notebook-cellEditorBackground: #f2f2f2; + --vscode-notebook-editorBackground: #f5f5f5; + --vscode-keybindingTable-headerBackground: rgba(97, 97, 97, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(97, 97, 97, 0.04); + --vscode-debugTokenExpression-name: #9b46b0; + --vscode-debugTokenExpression-value: rgba(108, 108, 108, 0.8); + --vscode-debugTokenExpression-string: #a31515; + --vscode-debugTokenExpression-boolean: #0000ff; + --vscode-debugTokenExpression-number: #098658; + --vscode-debugTokenExpression-error: #e51400; + --vscode-debugView-exceptionLabelForeground: #ffffff; + --vscode-debugView-exceptionLabelBackground: #a31515; + --vscode-debugView-stateLabelForeground: #616161; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #1a85ff; + --vscode-debugConsole-warningForeground: #bf8803; + --vscode-debugConsole-errorForeground: #f1897f; + --vscode-debugConsole-sourceForeground: #616161; + --vscode-debugConsoleInputIcon-foreground: #616161; + --vscode-debugIcon-pauseForeground: #007acc; + --vscode-debugIcon-stopForeground: #a1260d; + --vscode-debugIcon-disconnectForeground: #a1260d; + --vscode-debugIcon-restartForeground: #388a34; + --vscode-debugIcon-stepOverForeground: #007acc; + --vscode-debugIcon-stepIntoForeground: #007acc; + --vscode-debugIcon-stepOutForeground: #007acc; + --vscode-debugIcon-continueForeground: #007acc; + --vscode-debugIcon-stepBackForeground: #007acc; + --vscode-scm-providerBorder: #c8c8c8; + --vscode-extensionButton-background: #705697; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #5a4579; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #705697; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: #5a4579; + --vscode-extensionIcon-starForeground: #df6100; + --vscode-extensionIcon-verifiedForeground: #006ab1; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #b51e78; + --vscode-terminal-ansiBlack: #000000; + --vscode-terminal-ansiRed: #cd3131; + --vscode-terminal-ansiGreen: #00bc00; + --vscode-terminal-ansiYellow: #949800; + --vscode-terminal-ansiBlue: #0451a5; + --vscode-terminal-ansiMagenta: #bc05bc; + --vscode-terminal-ansiCyan: #0598bc; + --vscode-terminal-ansiWhite: #555555; + --vscode-terminal-ansiBrightBlack: #666666; + --vscode-terminal-ansiBrightRed: #cd3131; + --vscode-terminal-ansiBrightGreen: #14ce14; + --vscode-terminal-ansiBrightYellow: #b5ba00; + --vscode-terminal-ansiBrightBlue: #0451a5; + --vscode-terminal-ansiBrightMagenta: #bc05bc; + --vscode-terminal-ansiBrightCyan: #0598bc; + --vscode-terminal-ansiBrightWhite: #a5a5a5; + --vscode-interactive-activeCodeBorder: #705697; + --vscode-interactive-inactiveCodeBorder: #d3dbcd; + --vscode-gitDecoration-addedResourceForeground: #587c0c; + --vscode-gitDecoration-modifiedResourceForeground: #895503; + --vscode-gitDecoration-deletedResourceForeground: #ad0707; + --vscode-gitDecoration-renamedResourceForeground: #007100; + --vscode-gitDecoration-untrackedResourceForeground: #007100; + --vscode-gitDecoration-ignoredResourceForeground: #8e8e90; + --vscode-gitDecoration-stageModifiedResourceForeground: #895503; + --vscode-gitDecoration-stageDeletedResourceForeground: #ad0707; + --vscode-gitDecoration-conflictingResourceForeground: #ad0707; + --vscode-gitDecoration-submoduleResourceForeground: #1258a7; +} diff --git a/vendor/mynah-ui/example/src/styles/themes/light-solarized.scss b/vendor/mynah-ui/example/src/styles/themes/light-solarized.scss new file mode 100644 index 00000000..849bcd44 --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/themes/light-solarized.scss @@ -0,0 +1,607 @@ +html[theme='light-solarized']:root { + --vscode-foreground: #616161; + --vscode-disabledForeground: rgba(97, 97, 97, 0.5); + --vscode-errorForeground: #a1260d; + --vscode-descriptionForeground: #717171; + --vscode-icon-foreground: #424242; + --vscode-focusBorder: #b49471; + --vscode-selection-background: rgba(135, 139, 145, 0.5); + --vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18); + --vscode-textLink-foreground: #006ab1; + --vscode-textLink-activeForeground: #006ab1; + --vscode-textPreformat-foreground: #a31515; + --vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1); + --vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5); + --vscode-textCodeBlock-background: rgba(220, 220, 220, 0.4); + --vscode-widget-shadow: rgba(0, 0, 0, 0.16); + --vscode-input-background: #ddd6c1; + --vscode-input-foreground: #586e75; + --vscode-inputOption-activeBorder: #d3af86; + --vscode-inputOption-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-inputOption-activeBackground: rgba(180, 148, 113, 0.2); + --vscode-inputOption-activeForeground: #000000; + --vscode-input-placeholderForeground: rgba(88, 110, 117, 0.67); + --vscode-inputValidation-infoBackground: #d6ecf2; + --vscode-inputValidation-infoBorder: #007acc; + --vscode-inputValidation-warningBackground: #f6f5d2; + --vscode-inputValidation-warningBorder: #b89500; + --vscode-inputValidation-errorBackground: #f2dede; + --vscode-inputValidation-errorBorder: #be1100; + --vscode-dropdown-background: #eee8d5; + --vscode-dropdown-foreground: #616161; + --vscode-dropdown-border: #d3af86; + --vscode-button-foreground: #ffffff; + --vscode-button-separator: rgba(255, 255, 255, 0.4); + --vscode-button-background: #ac9d57; + --vscode-button-hoverBackground: #8b7e44; + --vscode-button-secondaryForeground: #ffffff; + --vscode-button-secondaryBackground: #5f6a79; + --vscode-button-secondaryHoverBackground: #4c5561; + --vscode-badge-background: rgba(181, 137, 0, 0.67); + --vscode-badge-foreground: #333333; + --vscode-scrollbar-shadow: #dddddd; + --vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-progressBar-background: #b58900; + --vscode-editorError-foreground: #e51400; + --vscode-editorWarning-foreground: #bf8803; + --vscode-editorInfo-foreground: #1a85ff; + --vscode-editorHint-foreground: #6c6c6c; + --vscode-sash-hoverBorder: #b49471; + --vscode-editor-background: #fdf6e3; + --vscode-editor-foreground: #657b83; + --vscode-editorStickyScroll-background: #fdf6e3; + --vscode-editorStickyScrollHover-background: #f0f0f0; + --vscode-editorWidget-background: #eee8d5; + --vscode-editorWidget-foreground: #616161; + --vscode-editorWidget-border: #c8c8c8; + --vscode-quickInput-background: #eee8d5; + --vscode-quickInput-foreground: #616161; + --vscode-quickInputTitle-background: rgba(0, 0, 0, 0.06); + --vscode-pickerGroup-foreground: rgba(42, 161, 152, 0.6); + --vscode-pickerGroup-border: rgba(42, 161, 152, 0.6); + --vscode-keybindingLabel-background: rgba(221, 221, 221, 0.4); + --vscode-keybindingLabel-foreground: #555555; + --vscode-keybindingLabel-border: rgba(204, 204, 204, 0.4); + --vscode-keybindingLabel-bottomBorder: rgba(187, 187, 187, 0.4); + --vscode-editor-selectionBackground: #eee8d5; + --vscode-editor-inactiveSelectionBackground: rgba(238, 232, 213, 0.5); + --vscode-editor-selectionHighlightBackground: rgba(243, 239, 225, 0.6); + --vscode-editor-findMatchBackground: #a8ac94; + --vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3); + --vscode-searchEditor-findMatchBackground: rgba(234, 92, 0, 0.22); + --vscode-search-resultsInfoForeground: #616161; + --vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15); + --vscode-editorHoverWidget-background: #ccc4b0; + --vscode-editorHoverWidget-foreground: #616161; + --vscode-editorHoverWidget-border: #c8c8c8; + --vscode-editorHoverWidget-statusBarBackground: #c5bba5; + --vscode-editorLink-activeForeground: #0000ff; + --vscode-editorInlayHint-foreground: #969696; + --vscode-editorInlayHint-background: rgba(181, 137, 0, 0.07); + --vscode-editorInlayHint-typeForeground: #969696; + --vscode-editorInlayHint-typeBackground: rgba(181, 137, 0, 0.07); + --vscode-editorInlayHint-parameterForeground: #969696; + --vscode-editorInlayHint-parameterBackground: rgba(181, 137, 0, 0.07); + --vscode-editorLightBulb-foreground: #ddb100; + --vscode-editorLightBulbAutoFix-foreground: #007acc; + --vscode-diffEditor-insertedTextBackground: rgba(156, 204, 44, 0.25); + --vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-insertedLineBackground: rgba(155, 185, 85, 0.2); + --vscode-diffEditor-removedLineBackground: rgba(255, 0, 0, 0.2); + --vscode-diffEditor-diagonalFill: rgba(34, 34, 34, 0.2); + --vscode-diffEditor-unchangedRegionBackground: #e4e4e4; + --vscode-diffEditor-unchangedRegionForeground: #4d4c4c; + --vscode-diffEditor-unchangedCodeBackground: rgba(184, 184, 184, 0.16); + --vscode-list-focusOutline: #b49471; + --vscode-list-activeSelectionBackground: #dfca88; + --vscode-list-activeSelectionForeground: #6c6c6c; + --vscode-list-inactiveSelectionBackground: #d1cbb8; + --vscode-list-hoverBackground: rgba(223, 202, 136, 0.27); + --vscode-list-dropBackground: #d6ebff; + --vscode-list-highlightForeground: #b58900; + --vscode-list-focusHighlightForeground: #b58900; + --vscode-list-invalidItemForeground: #b89500; + --vscode-list-errorForeground: #b01011; + --vscode-list-warningForeground: #855f00; + --vscode-listFilterWidget-background: #eee8d5; + --vscode-listFilterWidget-outline: rgba(0, 0, 0, 0); + --vscode-listFilterWidget-noMatchesOutline: #be1100; + --vscode-listFilterWidget-shadow: rgba(0, 0, 0, 0.16); + --vscode-list-filterMatchBackground: rgba(234, 92, 0, 0.33); + --vscode-tree-indentGuidesStroke: #a9a9a9; + --vscode-tree-inactiveIndentGuidesStroke: rgba(169, 169, 169, 0.4); + --vscode-tree-tableColumnsBorder: rgba(97, 97, 97, 0.13); + --vscode-tree-tableOddRowsBackground: rgba(97, 97, 97, 0.04); + --vscode-list-deemphasizedForeground: #8e8e90; + --vscode-checkbox-background: #eee8d5; + --vscode-checkbox-selectBackground: #eee8d5; + --vscode-checkbox-foreground: #616161; + --vscode-checkbox-border: #d3af86; + --vscode-checkbox-selectBorder: #424242; + --vscode-quickInputList-focusForeground: #6c6c6c; + --vscode-quickInputList-focusBackground: rgba(223, 202, 136, 0.4); + --vscode-menu-foreground: #616161; + --vscode-menu-background: #eee8d5; + --vscode-menu-selectionForeground: #6c6c6c; + --vscode-menu-selectionBackground: #dfca88; + --vscode-menu-separatorBackground: #d4d4d4; + --vscode-toolbar-hoverBackground: rgba(184, 184, 184, 0.31); + --vscode-toolbar-activeBackground: rgba(166, 166, 166, 0.31); + --vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2); + --vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5); + --vscode-breadcrumb-foreground: rgba(97, 97, 97, 0.8); + --vscode-breadcrumb-background: #fdf6e3; + --vscode-breadcrumb-focusForeground: #4e4e4e; + --vscode-breadcrumb-activeSelectionForeground: #4e4e4e; + --vscode-breadcrumbPicker-background: #eee8d5; + --vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5); + --vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2); + --vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5); + --vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2); + --vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4); + --vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16); + --vscode-editorOverviewRuler-currentContentForeground: rgba(64, 200, 174, 0.5); + --vscode-editorOverviewRuler-incomingContentForeground: rgba(64, 166, 255, 0.5); + --vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4); + --vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-editorOverviewRuler-selectionHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-minimap-findMatchHighlight: #d18616; + --vscode-minimap-selectionOccurrenceHighlight: #c9c9c9; + --vscode-minimap-selectionHighlight: #eee8d5; + --vscode-minimap-errorHighlight: rgba(255, 18, 18, 0.7); + --vscode-minimap-warningHighlight: #bf8803; + --vscode-minimap-foregroundOpacity: #000000; + --vscode-minimapSlider-background: rgba(100, 100, 100, 0.2); + --vscode-minimapSlider-hoverBackground: rgba(100, 100, 100, 0.35); + --vscode-minimapSlider-activeBackground: rgba(0, 0, 0, 0.3); + --vscode-problemsErrorIcon-foreground: #e51400; + --vscode-problemsWarningIcon-foreground: #bf8803; + --vscode-problemsInfoIcon-foreground: #1a85ff; + --vscode-charts-foreground: #616161; + --vscode-charts-lines: rgba(97, 97, 97, 0.5); + --vscode-charts-red: #e51400; + --vscode-charts-blue: #1a85ff; + --vscode-charts-yellow: #bf8803; + --vscode-charts-orange: #d18616; + --vscode-charts-green: #388a34; + --vscode-charts-purple: #652d90; + --vscode-diffEditor-move\.border: rgba(139, 139, 139, 0.61); + --vscode-diffEditor-moveActive\.border: #ffa500; + --vscode-symbolIcon-arrayForeground: #616161; + --vscode-symbolIcon-booleanForeground: #616161; + --vscode-symbolIcon-classForeground: #d67e00; + --vscode-symbolIcon-colorForeground: #616161; + --vscode-symbolIcon-constantForeground: #616161; + --vscode-symbolIcon-constructorForeground: #652d90; + --vscode-symbolIcon-enumeratorForeground: #d67e00; + --vscode-symbolIcon-enumeratorMemberForeground: #007acc; + --vscode-symbolIcon-eventForeground: #d67e00; + --vscode-symbolIcon-fieldForeground: #007acc; + --vscode-symbolIcon-fileForeground: #616161; + --vscode-symbolIcon-folderForeground: #616161; + --vscode-symbolIcon-functionForeground: #652d90; + --vscode-symbolIcon-interfaceForeground: #007acc; + --vscode-symbolIcon-keyForeground: #616161; + --vscode-symbolIcon-keywordForeground: #616161; + --vscode-symbolIcon-methodForeground: #652d90; + --vscode-symbolIcon-moduleForeground: #616161; + --vscode-symbolIcon-namespaceForeground: #616161; + --vscode-symbolIcon-nullForeground: #616161; + --vscode-symbolIcon-numberForeground: #616161; + --vscode-symbolIcon-objectForeground: #616161; + --vscode-symbolIcon-operatorForeground: #616161; + --vscode-symbolIcon-packageForeground: #616161; + --vscode-symbolIcon-propertyForeground: #616161; + --vscode-symbolIcon-referenceForeground: #616161; + --vscode-symbolIcon-snippetForeground: #616161; + --vscode-symbolIcon-stringForeground: #616161; + --vscode-symbolIcon-structForeground: #616161; + --vscode-symbolIcon-textForeground: #616161; + --vscode-symbolIcon-typeParameterForeground: #616161; + --vscode-symbolIcon-unitForeground: #616161; + --vscode-symbolIcon-variableForeground: #007acc; + --vscode-actionBar-toggledBackground: rgba(180, 148, 113, 0.2); + --vscode-editorHoverWidget-highlightForeground: #b58900; + --vscode-editor-lineHighlightBackground: #eee8d5; + --vscode-editor-lineHighlightBorder: #eeeeee; + --vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-editor-symbolHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-editorCursor-foreground: #657b83; + --vscode-editorWhitespace-foreground: rgba(88, 110, 117, 0.5); + --vscode-editorLineNumber-foreground: #237893; + --vscode-editorIndentGuide-background: rgba(88, 110, 117, 0.5); + --vscode-editorIndentGuide-activeBackground: rgba(8, 30, 37, 0.5); + --vscode-editorIndentGuide-background1: rgba(88, 110, 117, 0.5); + --vscode-editorIndentGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground1: rgba(8, 30, 37, 0.5); + --vscode-editorIndentGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorIndentGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorActiveLineNumber-foreground: #0b216f; + --vscode-editorLineNumber-activeForeground: #567983; + --vscode-editorRuler-foreground: #d3d3d3; + --vscode-editorCodeLens-foreground: #919191; + --vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1); + --vscode-editorBracketMatch-border: #b9b9b9; + --vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3); + --vscode-editorGutter-background: #fdf6e3; + --vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47); + --vscode-editorGhostText-foreground: rgba(0, 0, 0, 0.47); + --vscode-editorOverviewRuler-rangeHighlightForeground: rgba(0, 122, 204, 0.6); + --vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7); + --vscode-editorOverviewRuler-warningForeground: #bf8803; + --vscode-editorOverviewRuler-infoForeground: #1a85ff; + --vscode-editorBracketHighlight-foreground1: #0431fa; + --vscode-editorBracketHighlight-foreground2: #319331; + --vscode-editorBracketHighlight-foreground3: #7b3814; + --vscode-editorBracketHighlight-foreground4: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground5: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-foreground6: rgba(0, 0, 0, 0); + --vscode-editorBracketHighlight-unexpectedBracket\.foreground: rgba(255, 18, 18, 0.8); + --vscode-editorBracketPairGuide-background1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-background6: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground1: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground2: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground3: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground4: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground5: rgba(0, 0, 0, 0); + --vscode-editorBracketPairGuide-activeBackground6: rgba(0, 0, 0, 0); + --vscode-editorUnicodeHighlight-border: #cea33d; + --vscode-editorUnicodeHighlight-background: rgba(206, 163, 61, 0.08); + --vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0; + --vscode-editor-foldBackground: rgba(238, 232, 213, 0.3); + --vscode-editorGutter-foldingControlForeground: #424242; + --vscode-editor-linkedEditingBackground: rgba(255, 0, 0, 0.3); + --vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25); + --vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25); + --vscode-editor-wordHighlightTextBackground: rgba(87, 87, 87, 0.25); + --vscode-editorOverviewRuler-wordHighlightForeground: rgba(160, 160, 160, 0.8); + --vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(192, 160, 192, 0.8); + --vscode-editorOverviewRuler-wordHighlightTextForeground: rgba(160, 160, 160, 0.8); + --vscode-peekViewTitle-background: #eee8d5; + --vscode-peekViewTitleLabel-foreground: #000000; + --vscode-peekViewTitleDescription-foreground: #616161; + --vscode-peekView-border: #b58900; + --vscode-peekViewResult-background: #eee8d5; + --vscode-peekViewResult-lineForeground: #646465; + --vscode-peekViewResult-fileForeground: #1e1e1e; + --vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2); + --vscode-peekViewResult-selectionForeground: #6c6c6c; + --vscode-peekViewEditor-background: #fffbf2; + --vscode-peekViewEditorGutter-background: #fffbf2; + --vscode-peekViewEditorStickyScroll-background: #fffbf2; + --vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3); + --vscode-peekViewEditor-matchHighlightBackground: rgba(119, 68, 170, 0.25); + --vscode-editorMarkerNavigationError-background: #e51400; + --vscode-editorMarkerNavigationError-headerBackground: rgba(229, 20, 0, 0.1); + --vscode-editorMarkerNavigationWarning-background: #bf8803; + --vscode-editorMarkerNavigationWarning-headerBackground: rgba(191, 136, 3, 0.1); + --vscode-editorMarkerNavigationInfo-background: #1a85ff; + --vscode-editorMarkerNavigationInfo-headerBackground: rgba(26, 133, 255, 0.1); + --vscode-editorMarkerNavigation-background: #fdf6e3; + --vscode-editorSuggestWidget-background: #eee8d5; + --vscode-editorSuggestWidget-border: #c8c8c8; + --vscode-editorSuggestWidget-foreground: #657b83; + --vscode-editorSuggestWidget-selectedForeground: #6c6c6c; + --vscode-editorSuggestWidget-selectedBackground: rgba(223, 202, 136, 0.4); + --vscode-editorSuggestWidget-highlightForeground: #b58900; + --vscode-editorSuggestWidget-focusHighlightForeground: #b58900; + --vscode-editorSuggestWidgetStatus-foreground: rgba(101, 123, 131, 0.5); + --vscode-tab-activeBackground: #fdf6e3; + --vscode-tab-unfocusedActiveBackground: #fdf6e3; + --vscode-tab-inactiveBackground: #d3cbb7; + --vscode-tab-unfocusedInactiveBackground: #d3cbb7; + --vscode-tab-activeForeground: #333333; + --vscode-tab-inactiveForeground: #586e75; + --vscode-tab-unfocusedActiveForeground: rgba(51, 51, 51, 0.7); + --vscode-tab-unfocusedInactiveForeground: rgba(88, 110, 117, 0.5); + --vscode-tab-border: #ddd6c1; + --vscode-tab-lastPinnedBorder: #fdf6e3; + --vscode-tab-activeModifiedBorder: #cb4b16; + --vscode-tab-inactiveModifiedBorder: rgba(203, 75, 22, 0.5); + --vscode-tab-unfocusedActiveModifiedBorder: rgba(203, 75, 22, 0.7); + --vscode-tab-unfocusedInactiveModifiedBorder: rgba(203, 75, 22, 0.25); + --vscode-editorPane-background: #fdf6e3; + --vscode-editorGroupHeader-tabsBackground: #d9d2c2; + --vscode-editorGroupHeader-noTabsBackground: #fdf6e3; + --vscode-editorGroup-border: #ddd6c1; + --vscode-editorGroup-dropBackground: rgba(221, 214, 193, 0.67); + --vscode-editorGroup-dropIntoPromptForeground: #616161; + --vscode-editorGroup-dropIntoPromptBackground: #eee8d5; + --vscode-sideBySideEditor-horizontalBorder: #ddd6c1; + --vscode-sideBySideEditor-verticalBorder: #ddd6c1; + --vscode-panel-background: #fdf6e3; + --vscode-panel-border: #ddd6c1; + --vscode-panelTitle-activeForeground: #424242; + --vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, 0.75); + --vscode-panelTitle-activeBorder: #424242; + --vscode-panelInput-border: #dddddd; + --vscode-panel-dropBorder: #424242; + --vscode-panelSection-dropBackground: rgba(221, 214, 193, 0.67); + --vscode-panelSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-panelSection-border: #ddd6c1; + --vscode-banner-background: #c6a435; + --vscode-banner-foreground: #6c6c6c; + --vscode-banner-iconForeground: #1a85ff; + --vscode-statusBar-foreground: #586e75; + --vscode-statusBar-noFolderForeground: #586e75; + --vscode-statusBar-background: #eee8d5; + --vscode-statusBar-noFolderBackground: #eee8d5; + --vscode-statusBar-focusBorder: #586e75; + --vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18); + --vscode-statusBarItem-focusBorder: #586e75; + --vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-hoverForeground: #586e75; + --vscode-statusBarItem-compactHoverBackground: rgba(255, 255, 255, 0.2); + --vscode-statusBarItem-prominentForeground: #586e75; + --vscode-statusBarItem-prominentBackground: #ddd6c1; + --vscode-statusBarItem-prominentHoverForeground: #586e75; + --vscode-statusBarItem-prominentHoverBackground: rgba(221, 214, 193, 0.6); + --vscode-statusBarItem-errorBackground: #611708; + --vscode-statusBarItem-errorForeground: #ffffff; + --vscode-statusBarItem-errorHoverForeground: #586e75; + --vscode-statusBarItem-errorHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-warningBackground: #725102; + --vscode-statusBarItem-warningForeground: #ffffff; + --vscode-statusBarItem-warningHoverForeground: #586e75; + --vscode-statusBarItem-warningHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-activityBar-background: #ddd6c1; + --vscode-activityBar-foreground: #584c27; + --vscode-activityBar-inactiveForeground: rgba(88, 76, 39, 0.4); + --vscode-activityBar-activeBorder: #584c27; + --vscode-activityBar-dropBorder: #584c27; + --vscode-activityBarBadge-background: #b58900; + --vscode-activityBarBadge-foreground: #ffffff; + --vscode-profileBadge-background: #c4c4c4; + --vscode-profileBadge-foreground: #333333; + --vscode-statusBarItem-remoteBackground: #ac9d57; + --vscode-statusBarItem-remoteForeground: #ffffff; + --vscode-statusBarItem-remoteHoverForeground: #586e75; + --vscode-statusBarItem-remoteHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-statusBarItem-offlineBackground: #6c1717; + --vscode-statusBarItem-offlineForeground: #ffffff; + --vscode-statusBarItem-offlineHoverForeground: #586e75; + --vscode-statusBarItem-offlineHoverBackground: rgba(255, 255, 255, 0.12); + --vscode-extensionBadge-remoteBackground: #b58900; + --vscode-extensionBadge-remoteForeground: #ffffff; + --vscode-sideBar-background: #eee8d5; + --vscode-sideBarTitle-foreground: #586e75; + --vscode-sideBar-dropBackground: rgba(221, 214, 193, 0.67); + --vscode-sideBarSectionHeader-background: rgba(128, 128, 128, 0.2); + --vscode-titleBar-activeForeground: #333333; + --vscode-titleBar-inactiveForeground: rgba(51, 51, 51, 0.6); + --vscode-titleBar-activeBackground: #eee8d5; + --vscode-titleBar-inactiveBackground: rgba(238, 232, 213, 0.6); + --vscode-menubar-selectionForeground: #333333; + --vscode-menubar-selectionBackground: rgba(184, 184, 184, 0.31); + --vscode-notifications-foreground: #616161; + --vscode-notifications-background: #eee8d5; + --vscode-notificationLink-foreground: #006ab1; + --vscode-notificationCenterHeader-background: #e7dfc5; + --vscode-notifications-border: #e7dfc5; + --vscode-notificationsErrorIcon-foreground: #e51400; + --vscode-notificationsWarningIcon-foreground: #bf8803; + --vscode-notificationsInfoIcon-foreground: #1a85ff; + --vscode-commandCenter-foreground: #333333; + --vscode-commandCenter-activeForeground: #333333; + --vscode-commandCenter-inactiveForeground: rgba(51, 51, 51, 0.6); + --vscode-commandCenter-background: rgba(0, 0, 0, 0.05); + --vscode-commandCenter-activeBackground: rgba(0, 0, 0, 0.08); + --vscode-commandCenter-border: rgba(51, 51, 51, 0.2); + --vscode-commandCenter-activeBorder: rgba(51, 51, 51, 0.3); + --vscode-commandCenter-inactiveBorder: rgba(51, 51, 51, 0.15); + --vscode-chat-requestBorder: rgba(0, 0, 0, 0.1); + --vscode-chat-slashCommandBackground: rgba(181, 137, 0, 0.67); + --vscode-chat-slashCommandForeground: #333333; + --vscode-simpleFindWidget-sashBorder: #c8c8c8; + --vscode-commentsView-resolvedIcon: rgba(97, 97, 97, 0.5); + --vscode-commentsView-unresolvedIcon: #b49471; + --vscode-editorCommentsWidget-resolvedBorder: rgba(97, 97, 97, 0.5); + --vscode-editorCommentsWidget-unresolvedBorder: #b49471; + --vscode-editorCommentsWidget-rangeBackground: rgba(180, 148, 113, 0.1); + --vscode-editorCommentsWidget-rangeActiveBackground: rgba(180, 148, 113, 0.1); + --vscode-editorGutter-commentRangeForeground: #c9c2ac; + --vscode-editorOverviewRuler-commentForeground: #c9c2ac; + --vscode-editorOverviewRuler-commentUnresolvedForeground: #c9c2ac; + --vscode-editorGutter-commentGlyphForeground: #657b83; + --vscode-editorGutter-commentUnresolvedGlyphForeground: #657b83; + --vscode-debugToolBar-background: #ddd6c1; + --vscode-debugIcon-startForeground: #388a34; + --vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45); + --vscode-editor-focusedStackFrameHighlightBackground: rgba(206, 231, 206, 0.45); + --vscode-mergeEditor-change\.background: rgba(155, 185, 85, 0.2); + --vscode-mergeEditor-change\.word\.background: rgba(156, 204, 44, 0.4); + --vscode-mergeEditor-changeBase\.background: #ffcccc; + --vscode-mergeEditor-changeBase\.word\.background: #ffa3a3; + --vscode-mergeEditor-conflict\.unhandledUnfocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.unhandledFocused\.border: #ffa600; + --vscode-mergeEditor-conflict\.handledUnfocused\.border: rgba(134, 134, 134, 0.29); + --vscode-mergeEditor-conflict\.handledFocused\.border: rgba(193, 193, 193, 0.8); + --vscode-mergeEditor-conflict\.handled\.minimapOverViewRuler: rgba(173, 172, 168, 0.93); + --vscode-mergeEditor-conflict\.unhandled\.minimapOverViewRuler: #fcba03; + --vscode-mergeEditor-conflictingLines\.background: rgba(255, 234, 0, 0.28); + --vscode-mergeEditor-conflict\.input1\.background: rgba(64, 200, 174, 0.2); + --vscode-mergeEditor-conflict\.input2\.background: rgba(64, 166, 255, 0.2); + --vscode-settings-headerForeground: #444444; + --vscode-settings-settingsHeaderHoverForeground: rgba(68, 68, 68, 0.7); + --vscode-settings-modifiedItemIndicator: #66afe0; + --vscode-settings-headerBorder: #ddd6c1; + --vscode-settings-sashBorder: #ddd6c1; + --vscode-settings-dropdownBackground: #eee8d5; + --vscode-settings-dropdownForeground: #616161; + --vscode-settings-dropdownBorder: #d3af86; + --vscode-settings-dropdownListBorder: #c8c8c8; + --vscode-settings-checkboxBackground: #eee8d5; + --vscode-settings-checkboxForeground: #616161; + --vscode-settings-checkboxBorder: #d3af86; + --vscode-settings-textInputBackground: #ddd6c1; + --vscode-settings-textInputForeground: #586e75; + --vscode-settings-numberInputBackground: #ddd6c1; + --vscode-settings-numberInputForeground: #586e75; + --vscode-settings-focusedRowBackground: rgba(223, 202, 136, 0.16); + --vscode-settings-rowHoverBackground: rgba(223, 202, 136, 0.08); + --vscode-settings-focusedRowBorder: #b49471; + --vscode-terminal-background: #fdf6e3; + --vscode-terminal-foreground: #333333; + --vscode-terminal-selectionBackground: #eee8d5; + --vscode-terminal-inactiveSelectionBackground: rgba(238, 232, 213, 0.5); + --vscode-terminalCommandDecoration-defaultBackground: rgba(0, 0, 0, 0.25); + --vscode-terminalCommandDecoration-successBackground: #2090d3; + --vscode-terminalCommandDecoration-errorBackground: #e51400; + --vscode-terminalOverviewRuler-cursorForeground: rgba(160, 160, 160, 0.8); + --vscode-terminal-border: #ddd6c1; + --vscode-terminal-findMatchBackground: #a8ac94; + --vscode-terminal-hoverHighlightBackground: rgba(173, 214, 255, 0.07); + --vscode-terminal-findMatchHighlightBackground: rgba(234, 92, 0, 0.33); + --vscode-terminalOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49); + --vscode-terminal-dropBackground: rgba(221, 214, 193, 0.67); + --vscode-testing-iconFailed: #f14c4c; + --vscode-testing-iconErrored: #f14c4c; + --vscode-testing-iconPassed: #73c991; + --vscode-testing-runAction: #73c991; + --vscode-testing-iconQueued: #cca700; + --vscode-testing-iconUnset: #848484; + --vscode-testing-iconSkipped: #848484; + --vscode-testing-peekBorder: #e51400; + --vscode-testing-peekHeaderBackground: rgba(229, 20, 0, 0.1); + --vscode-testing-message\.error\.decorationForeground: #e51400; + --vscode-testing-message\.error\.lineBackground: rgba(255, 0, 0, 0.2); + --vscode-testing-message\.info\.decorationForeground: rgba(101, 123, 131, 0.5); + --vscode-welcomePage-tileBackground: #eee8d5; + --vscode-welcomePage-tileHoverBackground: #e1d7b5; + --vscode-welcomePage-tileBorder: rgba(0, 0, 0, 0.1); + --vscode-welcomePage-progress\.background: #ddd6c1; + --vscode-welcomePage-progress\.foreground: #006ab1; + --vscode-walkthrough-stepTitle\.foreground: #000000; + --vscode-walkThrough-embeddedEditorBackground: rgba(0, 0, 0, 0.08); + --vscode-inlineChat-background: #eee8d5; + --vscode-inlineChat-border: #c8c8c8; + --vscode-inlineChat-shadow: rgba(0, 0, 0, 0.16); + --vscode-inlineChat-regionHighlight: rgba(173, 214, 255, 0.15); + --vscode-inlineChatInput-border: #c8c8c8; + --vscode-inlineChatInput-focusBorder: #b49471; + --vscode-inlineChatInput-placeholderForeground: rgba(88, 110, 117, 0.67); + --vscode-inlineChatInput-background: #ddd6c1; + --vscode-inlineChatDiff-inserted: rgba(156, 204, 44, 0.13); + --vscode-inlineChatDiff-removed: rgba(255, 0, 0, 0.1); + --vscode-debugExceptionWidget-border: #ab395b; + --vscode-debugExceptionWidget-background: #ddd6c1; + --vscode-ports-iconRunningProcessForeground: rgba(42, 161, 152, 0.6); + --vscode-statusBar-debuggingBackground: #eee8d5; + --vscode-statusBar-debuggingForeground: #586e75; + --vscode-editor-inlineValuesForeground: rgba(0, 0, 0, 0.5); + --vscode-editor-inlineValuesBackground: rgba(255, 200, 0, 0.2); + --vscode-editorGutter-modifiedBackground: #2090d3; + --vscode-editorGutter-addedBackground: #48985d; + --vscode-editorGutter-deletedBackground: #e51400; + --vscode-minimapGutter-modifiedBackground: #2090d3; + --vscode-minimapGutter-addedBackground: #48985d; + --vscode-minimapGutter-deletedBackground: #e51400; + --vscode-editorOverviewRuler-modifiedForeground: rgba(32, 144, 211, 0.6); + --vscode-editorOverviewRuler-addedForeground: rgba(72, 152, 93, 0.6); + --vscode-editorOverviewRuler-deletedForeground: rgba(229, 20, 0, 0.6); + --vscode-debugIcon-breakpointForeground: #e51400; + --vscode-debugIcon-breakpointDisabledForeground: #848484; + --vscode-debugIcon-breakpointUnverifiedForeground: #848484; + --vscode-debugIcon-breakpointCurrentStackframeForeground: #be8700; + --vscode-debugIcon-breakpointStackframeForeground: #89d185; + --vscode-notebook-cellBorderColor: #d1cbb8; + --vscode-notebook-focusedEditorBorder: #b49471; + --vscode-notebookStatusSuccessIcon-foreground: #388a34; + --vscode-notebookEditorOverviewRuler-runningCellForeground: #388a34; + --vscode-notebookStatusErrorIcon-foreground: #a1260d; + --vscode-notebookStatusRunningIcon-foreground: #616161; + --vscode-notebook-cellToolbarSeparator: rgba(128, 128, 128, 0.35); + --vscode-notebook-selectedCellBackground: #d1cbb8; + --vscode-notebook-selectedCellBorder: #d1cbb8; + --vscode-notebook-focusedCellBorder: #b49471; + --vscode-notebook-inactiveFocusedCellBorder: #d1cbb8; + --vscode-notebook-cellStatusBarItemHoverBackground: rgba(0, 0, 0, 0.08); + --vscode-notebook-cellInsertionIndicator: #b49471; + --vscode-notebookScrollbarSlider-background: rgba(100, 100, 100, 0.4); + --vscode-notebookScrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7); + --vscode-notebookScrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6); + --vscode-notebook-symbolHighlightBackground: rgba(253, 255, 0, 0.2); + --vscode-notebook-cellEditorBackground: #f7f0e0; + --vscode-notebook-editorBackground: #fdf6e3; + --vscode-keybindingTable-headerBackground: rgba(97, 97, 97, 0.04); + --vscode-keybindingTable-rowsBackground: rgba(97, 97, 97, 0.04); + --vscode-debugTokenExpression-name: #9b46b0; + --vscode-debugTokenExpression-value: rgba(108, 108, 108, 0.8); + --vscode-debugTokenExpression-string: #a31515; + --vscode-debugTokenExpression-boolean: #0000ff; + --vscode-debugTokenExpression-number: #098658; + --vscode-debugTokenExpression-error: #e51400; + --vscode-debugView-exceptionLabelForeground: #ffffff; + --vscode-debugView-exceptionLabelBackground: #a31515; + --vscode-debugView-stateLabelForeground: #616161; + --vscode-debugView-stateLabelBackground: rgba(136, 136, 136, 0.27); + --vscode-debugView-valueChangedHighlight: #569cd6; + --vscode-debugConsole-infoForeground: #1a85ff; + --vscode-debugConsole-warningForeground: #bf8803; + --vscode-debugConsole-errorForeground: #a1260d; + --vscode-debugConsole-sourceForeground: #616161; + --vscode-debugConsoleInputIcon-foreground: #616161; + --vscode-debugIcon-pauseForeground: #007acc; + --vscode-debugIcon-stopForeground: #a1260d; + --vscode-debugIcon-disconnectForeground: #a1260d; + --vscode-debugIcon-restartForeground: #388a34; + --vscode-debugIcon-stepOverForeground: #007acc; + --vscode-debugIcon-stepIntoForeground: #007acc; + --vscode-debugIcon-stepOutForeground: #007acc; + --vscode-debugIcon-continueForeground: #007acc; + --vscode-debugIcon-stepBackForeground: #007acc; + --vscode-scm-providerBorder: #c8c8c8; + --vscode-extensionButton-background: #ac9d57; + --vscode-extensionButton-foreground: #ffffff; + --vscode-extensionButton-hoverBackground: #8b7e44; + --vscode-extensionButton-separator: rgba(255, 255, 255, 0.4); + --vscode-extensionButton-prominentBackground: #b58900; + --vscode-extensionButton-prominentForeground: #ffffff; + --vscode-extensionButton-prominentHoverBackground: rgba(88, 76, 39, 0.67); + --vscode-extensionIcon-starForeground: #df6100; + --vscode-extensionIcon-verifiedForeground: #006ab1; + --vscode-extensionIcon-preReleaseForeground: #1d9271; + --vscode-extensionIcon-sponsorForeground: #b51e78; + --vscode-terminal-ansiBlack: #073642; + --vscode-terminal-ansiRed: #dc322f; + --vscode-terminal-ansiGreen: #859900; + --vscode-terminal-ansiYellow: #b58900; + --vscode-terminal-ansiBlue: #268bd2; + --vscode-terminal-ansiMagenta: #d33682; + --vscode-terminal-ansiCyan: #2aa198; + --vscode-terminal-ansiWhite: #eee8d5; + --vscode-terminal-ansiBrightBlack: #002b36; + --vscode-terminal-ansiBrightRed: #cb4b16; + --vscode-terminal-ansiBrightGreen: #586e75; + --vscode-terminal-ansiBrightYellow: #657b83; + --vscode-terminal-ansiBrightBlue: #839496; + --vscode-terminal-ansiBrightMagenta: #6c71c4; + --vscode-terminal-ansiBrightCyan: #93a1a1; + --vscode-terminal-ansiBrightWhite: #fdf6e3; + --vscode-interactive-activeCodeBorder: #b58900; + --vscode-interactive-inactiveCodeBorder: #d1cbb8; + --vscode-gitDecoration-addedResourceForeground: #587c0c; + --vscode-gitDecoration-modifiedResourceForeground: #895503; + --vscode-gitDecoration-deletedResourceForeground: #ad0707; + --vscode-gitDecoration-renamedResourceForeground: #007100; + --vscode-gitDecoration-untrackedResourceForeground: #007100; + --vscode-gitDecoration-ignoredResourceForeground: #8e8e90; + --vscode-gitDecoration-stageModifiedResourceForeground: #895503; + --vscode-gitDecoration-stageDeletedResourceForeground: #ad0707; + --vscode-gitDecoration-conflictingResourceForeground: #ad0707; + --vscode-gitDecoration-submoduleResourceForeground: #1258a7; +} diff --git a/vendor/mynah-ui/example/src/styles/variables.scss b/vendor/mynah-ui/example/src/styles/variables.scss new file mode 100644 index 00000000..ecae089a --- /dev/null +++ b/vendor/mynah-ui/example/src/styles/variables.scss @@ -0,0 +1,6 @@ +:root { + font-size: 14px !important; + --mynah-font-family: system-ui; + --skeleton-default: var(--mynah-color-text-weak); + --skeleton-selected: var(--mynah-color-button); +} diff --git a/vendor/mynah-ui/example/src/theme-builder/base-theme-dark-config.json b/vendor/mynah-ui/example/src/theme-builder/base-theme-dark-config.json new file mode 100644 index 00000000..fd22ae6c --- /dev/null +++ b/vendor/mynah-ui/example/src/theme-builder/base-theme-dark-config.json @@ -0,0 +1,451 @@ +{ + "--mynah-max-width": { + "type": "measurement", + "description": "Max width for mynah-ui container", + "units": ["px", "rem", "vw", ""], + "unit": "px", + "category": "sizing", + "value": "2560" + }, + "--mynah-sizing-base": { + "type": "measurement", + "description": "Base spacing value used on paddings, margins etc.", + "units": ["px", "rem", ""], + "unit": "rem", + "category": "sizing", + "value": "0.2" + }, + "--mynah-chat-wrapper-spacing": { + "type": "text", + "description": "Chat wrapper spacing value used on paddings and the distance between cards", + "category": "sizing", + "value": "var(--mynah-sizing-4)" + }, + "--mynah-font-family": { + "type": "text", + "description": "Base font-family", + "category": "font-family", + "value": "system-ui, -apple-system, sans-serif" + }, + "--mynah-syntax-code-font-family": { + "type": "text", + "description": "Code blocks' font-family", + "category": "font-family", + "value": "monospace" + }, + "--mynah-color-text-default": { + "type": "color", + "description": "Default text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#cad2f2" + }, + "--mynah-color-text-alternate": { + "type": "color", + "description": "Default text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-color-text-strong": { + "type": "color", + "description": "Strong text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-color-text-weak": { + "type": "color", + "description": "Light text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#84889a" + }, + "--mynah-color-text-link": { + "type": "color", + "description": "Link text color", + "category": "text-color", + "alpha": "100", + "value": "#98a8ec" + }, + "--mynah-color-text-input": { + "type": "color", + "description": "Input text color used in input fields", + "category": "text-color", + "alpha": "100", + "value": "#ebebeb" + }, + "--mynah-color-light": { + "type": "color", + "description": "Light shade text color used in various places", + "category": "text-color", + "alpha": "5", + "value": "#000000" + }, + "--mynah-color-highlight": { + "type": "color", + "description": "Highlighted text background color", + "category": "text-color", + "alpha": "25", + "value": "#d0d4e7" + }, + "--mynah-color-highlight-text": { + "type": "color", + "description": "Highlighted text foreground/text color", + "category": "text-color", + "alpha": "100", + "value": "#222849" + }, + "--mynah-color-border-default": { + "type": "color", + "description": "Default border color used in several places", + "category": "border-style", + "alpha": "15", + "value": "#8797d9" + }, + "--mynah-button-border-width": { + "type": "measurement", + "description": "Button border width", + "units": ["px", "rem", ""], + "unit": "px", + "category": "border-style", + "value": "1" + }, + "--mynah-border-width": { + "type": "measurement", + "description": "Default border width used in several places", + "units": ["px", "rem", ""], + "unit": "px", + "category": "border-style", + "value": "1" + }, + "--mynah-color-syntax-variable": { + "type": "color", + "description": "Variable color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#9b46b0" + }, + "--mynah-color-syntax-function": { + "type": "color", + "description": "Function declaration color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#6b89ff" + }, + "--mynah-color-syntax-operator": { + "type": "color", + "description": "Operator color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#9b46b0" + }, + "--mynah-color-syntax-attr-value": { + "type": "color", + "description": "Attribute value color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#b3beff" + }, + "--mynah-color-syntax-attr": { + "type": "color", + "description": "Attribute color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#e68484" + }, + "--mynah-color-syntax-property": { + "type": "color", + "description": "Property color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#0598bc" + }, + "--mynah-color-syntax-comment": { + "type": "color", + "description": "Comment color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#31c99b" + }, + "--mynah-color-syntax-code": { + "type": "color", + "description": "Default code text color inside code blocks and inline code", + "category": "syntax-color", + "alpha": "100", + "value": "#d4d8e8" + }, + "--mynah-color-syntax-bg": { + "type": "color", + "description": "Default background color of code blocks and inline code", + "category": "syntax-color", + "alpha": "40", + "value": "#495697" + }, + "--mynah-color-status-info": { + "type": "color", + "description": "Info color", + "category": "status-color", + "alpha": "100", + "value": "#4593b5" + }, + "--mynah-color-status-success": { + "type": "color", + "description": "Success color", + "category": "status-color", + "alpha": "100", + "value": "#49bc8a" + }, + "--mynah-color-status-warning": { + "type": "color", + "description": "Warning color", + "category": "status-color", + "alpha": "100", + "value": "#eec58c" + }, + "--mynah-color-status-error": { + "type": "color", + "description": "Error color", + "category": "status-color", + "alpha": "100", + "value": "#ea3e7a" + }, + "--mynah-color-bg": { + "type": "color", + "description": "Main background color ", + "category": "background-color", + "alpha": "100", + "value": "#292f47" + }, + "--mynah-color-tab-active": { + "type": "color", + "description": "Tab active background color", + "category": "background-color", + "alpha": "100", + "value": "#333a57" + }, + "--mynah-color-toggle": { + "type": "color", + "description": "Toggle background color", + "category": "background-color", + "alpha": "100", + "value": "#fafbfc" + }, + "--mynah-color-toggle-reverse": { + "type": "color", + "description": "Toggle foreground/text color", + "category": "background-color", + "alpha": "50", + "value": "#000000" + }, + "--mynah-color-button": { + "type": "color", + "description": "Button background color", + "category": "background-color", + "alpha": "100", + "value": "#5e6a9c" + }, + "--mynah-color-button-reverse": { + "type": "color", + "description": "Button foreground/text color", + "category": "background-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-color-alternate": { + "type": "color", + "description": "Alternative background color", + "category": "background-color", + "alpha": "100", + "value": "#5f6a79" + }, + "--mynah-color-alternate-reverse": { + "type": "color", + "description": "Alternative foreground/text color", + "category": "background-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-card-bg": { + "type": "color", + "description": "Card background color", + "category": "background-color", + "alpha": "100", + "value": "#333a57" + }, + "--mynah-card-bg-alternate": { + "type": "color", + "description": "Card alternate background color", + "category": "background-color", + "alpha": "100", + "value": "#5e6a9c" + }, + "--mynah-shadow-button": { + "type": "text", + "description": "Button shadows", + "category": "shadow", + "value": "0 5px 10px -10px rgba(0, 0, 0, 0.25)" + }, + "--mynah-shadow-card": { + "type": "text", + "description": "Card shadow", + "category": "shadow", + "value": "0 20px 25px -20px rgba(0, 0, 0, 0.5)" + }, + "--mynah-shadow-overlay": { + "type": "text", + "description": "Overlay shadows (notification, bottom popup etc.)", + "category": "shadow", + "value": "0 10px 35px -10px rgba(0, 0, 0, 0.75)" + }, + "--mynah-syntax-code-font-size": { + "type": "measurement", + "description": "Code block font size used code blocks (not the inline codes!)", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "0.9" + }, + "--mynah-syntax-code-line-height": { + "type": "measurement", + "description": "Line height for texts inside cards", + "category": "font-size", + "units": ["px", "rem", "em", ""], + "unit": "rem", + "value": "1.25" + }, + "--mynah-font-size-xxsmall": { + "type": "measurement", + "description": "The smallest font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "0.75" + }, + "--mynah-font-size-xsmall": { + "type": "measurement", + "description": "Extra small font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "0.85" + }, + "--mynah-font-size-small": { + "type": "measurement", + "description": "Small font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "0.95" + }, + "--mynah-font-size-medium": { + "type": "measurement", + "description": "Medium font size used on several places and the default font size in general which will follow the :root font size", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "1" + }, + "--mynah-font-size-large": { + "type": "measurement", + "description": "Large font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "1.125" + }, + "--mynah-line-height": { + "type": "measurement", + "description": "Line height for texts inside cards", + "category": "font-size", + "units": ["px", "rem", "em", ""], + "unit": "rem", + "value": "1.25" + }, + "--mynah-card-radius": { + "type": "measurement", + "description": "Border radius used on all Cards", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "var(--mynah-sizing-5)" + }, + "--mynah-card-radius-corner": { + "type": "measurement", + "description": "Border radius used on all Chat card edge bottom corner", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "0" + }, + "--mynah-button-radius": { + "type": "measurement", + "description": "Border radius used on buttons and follow up pills", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "var(--mynah-sizing-5)" + }, + "--mynah-input-radius": { + "type": "measurement", + "description": "Border radius used on inputs", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "var(--mynah-sizing-2)" + }, + "--mynah-main-wrapper-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 350ms cubic-bezier(0.83, 0, 0.17, 1)" + }, + "--mynah-bottom-panel-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 450ms cubic-bezier(0.25, 1, 0, 1)" + }, + "--mynah-short-rev-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 280ms cubic-bezier(0.35, 1, 0, 1)" + }, + "--mynah-very-short-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 300ms cubic-bezier(0.25, 1, 0, 1)" + }, + "--mynah-very-long-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 1000ms cubic-bezier(0.25, 1, 0, 1)" + }, + "--mynah-short-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 300ms cubic-bezier(0.85, 0.15, 0, 1)" + }, + "--mynah-short-transition-rev": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 280ms cubic-bezier(0.35, 1, 0, 1)" + }, + "--mynah-short-transition-rev-opacity": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "opacity 350ms cubic-bezier(0.35, 1, 0, 1)" + }, + "--mynah-text-flow-transition": { + "type": "text", + "category": "transition", + "description": "This is the transition animation for text flows during a stream update", + "value": "all 400ms cubic-bezier(0.35, 1.2, 0, 1), transform 400ms cubic-bezier(0.2, 1.05, 0, 1)" + } +} diff --git a/vendor/mynah-ui/example/src/theme-builder/base-theme-light-config.json b/vendor/mynah-ui/example/src/theme-builder/base-theme-light-config.json new file mode 100644 index 00000000..d0a8598c --- /dev/null +++ b/vendor/mynah-ui/example/src/theme-builder/base-theme-light-config.json @@ -0,0 +1,472 @@ +{ + "--mynah-max-width": { + "type": "measurement", + "description": "Max width for mynah-ui container", + "units": ["px", "rem", "vw", ""], + "unit": "px", + "category": "sizing", + "value": "2560" + }, + "--mynah-sizing-base": { + "type": "measurement", + "description": "Base spacing value used on paddings, margins etc.", + "units": ["px", "rem", ""], + "unit": "px", + "category": "sizing", + "value": "4" + }, + "--mynah-chat-wrapper-spacing": { + "type": "text", + "description": "Chat wrapper spacing value used on paddings and the distance between cards", + "category": "sizing", + "value": "var(--mynah-sizing-4)" + }, + "--mynah-font-family": { + "type": "text", + "description": "Base font-family", + "category": "font-family", + "value": "system-ui, -apple-system, sans-serif" + }, + "--mynah-syntax-code-font-family": { + "type": "text", + "description": "Code blocks' font-family", + "category": "font-family", + "value": "consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono Courier New, monospace" + }, + "--mynah-color-text-default": { + "type": "color", + "description": "Default text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#597688" + }, + "--mynah-color-text-alternate": { + "type": "color", + "description": "Alternate text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-color-text-strong": { + "type": "color", + "description": "Strong text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#296689" + }, + "--mynah-color-text-weak": { + "type": "color", + "description": "Light text color used in various places", + "category": "text-color", + "alpha": "100", + "value": "#bacad8" + }, + "--mynah-color-text-link": { + "type": "color", + "description": "Link text color", + "category": "text-color", + "alpha": "100", + "value": "#006ab1" + }, + "--mynah-color-text-input-border": { + "type": "color", + "description": "Border of the chat input textbox, when it does not have focus", + "category": "border-style", + "alpha": "100", + "value": "#fafcff" + }, + "--mynah-color-text-input-border-focused": { + "type": "color", + "description": "Border of the chat input textbox, when it has focus", + "category": "border-style", + "alpha": "100", + "value": "#e6e8eb" + }, + "--mynah-color-text-input": { + "type": "color", + "description": "Input text color used in input fields", + "category": "text-color", + "alpha": "100", + "value": "#517185" + }, + "--mynah-color-text-input-placeholder": { + "type": "color", + "description": "Link text color", + "category": "text-color", + "alpha": "100", + "value": "#bacad8" + }, + "--mynah-color-light": { + "type": "color", + "description": "Light shade text color used in various places", + "category": "text-color", + "alpha": "5", + "value": "#000000" + }, + "--mynah-color-highlight": { + "type": "color", + "description": "Highlighted text background color", + "category": "text-color", + "alpha": "100", + "value": "#fff3d6" + }, + "--mynah-color-highlight-text": { + "type": "color", + "description": "Highlighted text foreground/text color", + "category": "text-color", + "alpha": "100", + "value": "#886411" + }, + "--mynah-color-border-default": { + "type": "color", + "description": "Default border color used in several places", + "category": "border-style", + "alpha": "100", + "value": "#e6e8eb" + }, + "--mynah-button-border-width": { + "type": "measurement", + "description": "Button border width", + "units": ["px", "rem", ""], + "unit": "px", + "category": "border-style", + "value": "1" + }, + "--mynah-border-width": { + "type": "measurement", + "description": "Default border width used in several places", + "units": ["px", "rem", ""], + "unit": "px", + "category": "border-style", + "value": "1" + }, + "--mynah-color-syntax-variable": { + "type": "color", + "description": "Variable color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#7e009e" + }, + "--mynah-color-syntax-function": { + "type": "color", + "description": "Function declaration color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#4dc9ff" + }, + "--mynah-color-syntax-operator": { + "type": "color", + "description": "Operator color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#e60086" + }, + "--mynah-color-syntax-attr-value": { + "type": "color", + "description": "Attribute value color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#007acc" + }, + "--mynah-color-syntax-attr": { + "type": "color", + "description": "Attribute color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#dc0450" + }, + "--mynah-color-syntax-property": { + "type": "color", + "description": "Property color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#00d1e0" + }, + "--mynah-color-syntax-comment": { + "type": "color", + "description": "Comment color inside code blocks", + "category": "syntax-color", + "alpha": "100", + "value": "#0c923f" + }, + "--mynah-color-syntax-code": { + "type": "color", + "description": "Default code text color inside code blocks and inline code", + "category": "syntax-color", + "alpha": "100", + "value": "#0051a8" + }, + "--mynah-color-syntax-bg": { + "type": "color", + "description": "Default background color of code blocks and inline code", + "category": "syntax-color", + "alpha": "100", + "value": "#fafcff" + }, + "--mynah-color-status-info": { + "type": "color", + "description": "Info color", + "category": "status-color", + "alpha": "100", + "value": "#28a7e6" + }, + "--mynah-color-status-success": { + "type": "color", + "description": "Success color", + "category": "status-color", + "alpha": "100", + "value": "#36e281" + }, + "--mynah-color-status-warning": { + "type": "color", + "description": "Warning color", + "category": "status-color", + "alpha": "100", + "value": "#eca14b" + }, + "--mynah-color-status-error": { + "type": "color", + "description": "Error color", + "category": "status-color", + "alpha": "100", + "value": "#e60063" + }, + "--mynah-color-bg": { + "type": "color", + "description": "Main background color ", + "category": "background-color", + "alpha": "100", + "value": "#fafcff" + }, + "--mynah-color-tab-active": { + "type": "color", + "description": "Tab active background color", + "category": "background-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-color-toggle": { + "type": "color", + "description": "Toggle background color", + "category": "background-color", + "alpha": "100", + "value": "#fafbfc" + }, + "--mynah-color-toggle-reverse": { + "type": "color", + "description": "Toggle foreground/text color", + "category": "background-color", + "alpha": "50", + "value": "#000000" + }, + "--mynah-color-button": { + "type": "color", + "description": "Button background color", + "category": "background-color", + "alpha": "100", + "value": "#1e9ddc" + }, + "--mynah-color-button-reverse": { + "type": "color", + "description": "Button foreground/text color", + "category": "background-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-color-alternate": { + "type": "color", + "description": "Alternative background color", + "category": "background-color", + "alpha": "100", + "value": "#5f6a79" + }, + "--mynah-color-alternate-reverse": { + "type": "color", + "description": "Alternative foreground/text color", + "category": "background-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-card-bg": { + "type": "color", + "description": "Card background color", + "category": "background-color", + "alpha": "100", + "value": "#ffffff" + }, + "--mynah-card-bg-alternate": { + "type": "color", + "description": "Card alternate background color", + "category": "background-color", + "alpha": "100", + "value": "#a1b6d3" + }, + "--mynah-shadow-button": { + "type": "text", + "description": "Button shadows", + "category": "shadow", + "value": "0 5px 10px -10px rgba(0, 0, 0, 0.25)" + }, + "--mynah-shadow-card": { + "type": "text", + "description": "Card shadow", + "category": "shadow", + "value": "0 10px 20px -15px rgba(0, 0, 0, 0.25)" + }, + "--mynah-shadow-overlay": { + "type": "text", + "description": "Overlay shadows (notification, bottom popup etc.)", + "category": "shadow", + "value": "0 7px 27px -12px rgba(0, 0, 0, 0.35)" + }, + "--mynah-syntax-code-font-size": { + "type": "measurement", + "description": "Code block font size used code blocks (not the inline codes!)", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "1" + }, + "--mynah-syntax-code-line-height": { + "type": "measurement", + "description": "Line height for texts inside cards", + "category": "font-size", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "118%" + }, + "--mynah-font-size-xxsmall": { + "type": "measurement", + "description": "The smallest font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "0.825" + }, + "--mynah-font-size-xsmall": { + "type": "measurement", + "description": "Extra small font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "0.875" + }, + "--mynah-font-size-small": { + "type": "measurement", + "description": "Small font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "0.925" + }, + "--mynah-font-size-medium": { + "type": "measurement", + "description": "Medium font size used on several places and the default font size in general which will follow the :root font size", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "1" + }, + "--mynah-font-size-large": { + "type": "measurement", + "description": "Large font size used on several places", + "category": "font-size", + "units": ["px", "rem", ""], + "unit": "rem", + "value": "1.125" + }, + "--mynah-line-height": { + "type": "measurement", + "description": "Line height for texts inside cards", + "category": "font-size", + "units": ["px", "rem", "em", ""], + "unit": "rem", + "value": "1.25" + }, + "--mynah-card-radius": { + "type": "measurement", + "description": "Border radius used on all Cards", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "var(--mynah-sizing-5)" + }, + "--mynah-card-radius-corner": { + "type": "measurement", + "description": "Border radius used on all Chat card edge bottom corner", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "px", + "value": "0" + }, + "--mynah-button-radius": { + "type": "measurement", + "description": "Border radius used on buttons and follow up pills", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "var(--mynah-sizing-4)" + }, + "--mynah-input-radius": { + "type": "measurement", + "description": "Border radius used on inputs", + "category": "radius", + "units": ["px", "rem", "em", ""], + "unit": "", + "value": "var(--mynah-sizing-2)" + }, + "--mynah-main-wrapper-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 350ms cubic-bezier(0.83, 0, 0.17, 1)" + }, + "--mynah-bottom-panel-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 450ms cubic-bezier(0.25, 1, 0, 1)" + }, + "--mynah-short-rev-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 280ms cubic-bezier(0.35, 1, 0, 1)" + }, + "--mynah-very-short-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 300ms cubic-bezier(0.25, 1, 0, 1)" + }, + "--mynah-very-long-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 1000ms cubic-bezier(0.25, 1, 0, 1)" + }, + "--mynah-short-transition": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 300ms cubic-bezier(0.85, 0.15, 0, 1)" + }, + "--mynah-short-transition-rev": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "all 280ms cubic-bezier(0.35, 1, 0, 1)" + }, + "--mynah-short-transition-rev-opacity": { + "type": "text", + "category": "transition", + "description": "This is a transition animation used in several places", + "value": "opacity 350ms cubic-bezier(0.35, 1, 0, 1)" + }, + "--mynah-text-flow-transition": { + "type": "text", + "category": "transition", + "description": "This is the transition animation for text flows during a stream update", + "value": "all 400ms cubic-bezier(0.35, 1.2, 0, 1), transform 400ms cubic-bezier(0.2, 1.05, 0, 1)" + } +} diff --git a/vendor/mynah-ui/example/src/theme-builder/theme-builder.ts b/vendor/mynah-ui/example/src/theme-builder/theme-builder.ts new file mode 100644 index 00000000..1e9d49a9 --- /dev/null +++ b/vendor/mynah-ui/example/src/theme-builder/theme-builder.ts @@ -0,0 +1,380 @@ +import * as BaseConfigLight from './base-theme-light-config.json'; +import * as BaseConfigDark from './base-theme-dark-config.json'; +interface ConfigItem { + type: 'measurement' | 'text' | 'color'; + description?: string; + units?: string[]; + unit?: string; + category?: string; + alpha?: string; + value: string; +} +const categories = [ + 'sizing', + 'border-style', + 'font-size', + 'font-family', + 'text-color', + 'syntax-color', + 'status-color', + 'background-color', + 'shadow', + 'radius', + 'transition', +]; + +export class ThemeBuilder { + private themeSelector: HTMLSelectElement = document.querySelector('#theme-selector') as HTMLSelectElement; + private mainWrapper: HTMLElement = document.createElement('div'); + private inputsWrapper: HTMLElement = document.createElement('div'); + private buttonsWrapper: HTMLElement = document.createElement('div'); + private baseThemeType: 'light' | 'dark' = 'light'; + private currentConfig: Record = structuredClone(BaseConfigLight) as any; + constructor(selector: string | HTMLElement) { + delete this.currentConfig.default; + this.themeSelector.addEventListener('change', e => { + if (this.themeSelector.value.match('base-')) { + this.baseThemeType = this.themeSelector.value.replace('base-', '') as 'light' | 'dark'; + if (this.baseThemeType === 'light') { + this.currentConfig = structuredClone(BaseConfigLight) as any; + } else { + this.currentConfig = structuredClone(BaseConfigDark) as any; + } + this.inputsWrapper.innerHTML = ''; + this.fillInputWrapper(); + this.buildCssValues(); + } else if (this.themeSelector.value.match('dark-')) { + document.querySelector('body')?.classList.add('vscode-dark'); + } else { + document.querySelector('body')?.classList.remove('vscode-dark'); + } + document.querySelector('html')?.setAttribute('theme', this.themeSelector.value); + }); + this.mainWrapper.classList.add('mynah-ui-example-input-main-wrapper'); + this.inputsWrapper.classList.add('mynah-ui-example-input-items-wrapper'); + this.buttonsWrapper.classList.add('mynah-ui-example-input-buttons-wrapper'); + let parentWrapper: HTMLElement; + if (typeof selector === 'string') { + parentWrapper = document.querySelector(selector) ?? (document.querySelector('body') as HTMLElement); + } else { + parentWrapper = selector; + } + + this.mainWrapper.insertAdjacentElement('beforeend', this.inputsWrapper); + parentWrapper.insertAdjacentElement('beforeend', this.buttonsWrapper); + parentWrapper.insertAdjacentElement('beforeend', this.mainWrapper); + + this.mainWrapper.insertAdjacentHTML( + 'beforeend', + ` +

+ First, please select one of the Custom Themes from the themes list on the header bar. + After that you'll see the changes whenever you adjust one of the options below.
+ For measurement values (or anything other than colors) you can use current custom properties like the sizings.
+ First select (No Unit) option for the unit and then you can type any string into the value field. + And you can use custom properties as usual like var(--mynah-sizing-1) and the sizing values goes from 1 to 18. +

+ ` + ); + + this.fillInputWrapper(); + + const uploadThemeConfigFilePicker = document.createElement('input'); + uploadThemeConfigFilePicker.setAttribute('type', 'file'); + uploadThemeConfigFilePicker.setAttribute('accept', '.mynahuitc'); + uploadThemeConfigFilePicker.classList.add('hidden'); + uploadThemeConfigFilePicker.classList.add('config-operation'); + uploadThemeConfigFilePicker.classList.add('fill-state-always'); + uploadThemeConfigFilePicker.addEventListener('change', async () => { + const file = uploadThemeConfigFilePicker.files?.item(0); + if (file) { + const text = await file.text(); + try { + this.currentConfig = JSON.parse(text); + this.inputsWrapper.innerHTML = ''; + this.fillInputWrapper(); + this.buildCssValues(); + uploadThemeConfigFilePicker.value = ''; + } catch (err) { + console.warn("Coudln't read the JSON content"); + } + } + }); + + const downloadThemeConfigButton = document.createElement('button'); + downloadThemeConfigButton.innerHTML = 'Download Config'; + downloadThemeConfigButton.classList.add('mynah-button'); + downloadThemeConfigButton.classList.add('config-operation'); + downloadThemeConfigButton.classList.add('fill-state-always'); + downloadThemeConfigButton.addEventListener('click', () => { + download('mynah-ui-theme.mynahuitc', JSON.stringify(this.currentConfig)); + }); + + const resetThemeConfigButton = document.createElement('button'); + resetThemeConfigButton.innerHTML = 'Reset'; + resetThemeConfigButton.classList.add('mynah-button'); + resetThemeConfigButton.classList.add('config-operation'); + resetThemeConfigButton.classList.add('fill-state-always'); + resetThemeConfigButton.addEventListener('click', () => { + this.currentConfig = structuredClone(this.baseThemeType === 'light' ? BaseConfigLight : BaseConfigDark) as any; + this.inputsWrapper.innerHTML = ''; + this.fillInputWrapper(); + this.buildCssValues(); + }); + + const uploadThemeConfigButton = document.createElement('button'); + uploadThemeConfigButton.innerHTML = 'Upload Config'; + uploadThemeConfigButton.classList.add('mynah-button'); + uploadThemeConfigButton.classList.add('config-operation'); + uploadThemeConfigButton.classList.add('fill-state-always'); + uploadThemeConfigButton.addEventListener('click', () => { + uploadThemeConfigFilePicker.click(); + }); + + const downloadThemeButton = document.createElement('button'); + downloadThemeButton.innerHTML = 'Download Theme (CSS)'; + downloadThemeButton.classList.add('mynah-button'); + downloadThemeButton.classList.add('config-operation'); + downloadThemeButton.classList.add('fill-state-always'); + downloadThemeButton.addEventListener('click', () => { + download( + 'mynah-ui-theme.css', + `:root { + ${this.getCssCustomVars()} + }` + ); + }); + this.buttonsWrapper.insertAdjacentElement('beforeend', uploadThemeConfigFilePicker); + this.buttonsWrapper.insertAdjacentElement('beforeend', uploadThemeConfigButton); + this.buttonsWrapper.insertAdjacentElement('beforeend', downloadThemeConfigButton); + this.buttonsWrapper.insertAdjacentElement('beforeend', downloadThemeButton); + this.buttonsWrapper.insertAdjacentElement('beforeend', resetThemeConfigButton); + + this.buildCssValues(); + } + + private fillInputWrapper = () => { + categories.forEach(category => { + this.inputsWrapper.insertAdjacentHTML( + 'beforeend', + ` +
+

${category}

+
+ ` + ); + }); + + Object.keys(this.currentConfig).forEach((themeConfigKey: string) => { + const themeConfigItem = this.currentConfig[themeConfigKey] as ConfigItem; + switch (themeConfigItem.type) { + case 'text': + this.inputsWrapper.insertAdjacentElement( + 'beforeend', + themeInputText(themeConfigKey, themeConfigItem, value => { + this.currentConfig[themeConfigKey].value = value; + this.buildCssValues(); + }) + ); + break; + case 'measurement': + this.inputsWrapper.insertAdjacentElement( + 'beforeend', + themeInputMeasurement(themeConfigKey, themeConfigItem, (value, unit) => { + this.currentConfig[themeConfigKey].value = value; + this.currentConfig[themeConfigKey].unit = unit; + this.buildCssValues(); + }) + ); + break; + case 'color': + this.inputsWrapper.insertAdjacentElement( + 'beforeend', + themeInputColor(themeConfigKey, themeConfigItem, (hex, alpha) => { + this.currentConfig[themeConfigKey].value = hex; + this.currentConfig[themeConfigKey].alpha = alpha; + this.buildCssValues(); + }) + ); + break; + } + }); + }; + + private buildCssValues = () => { + (document.querySelector('#custom-style') as HTMLElement).innerHTML = ` + html[theme="base-${this.baseThemeType}"]:root { + font-size: 13px; + ${this.getCssCustomVars()} + } + `; + }; + + private getCssCustomVars = (): string => + Object.keys(this.currentConfig) + .map(configKey => { + const configItem = this.currentConfig[configKey]; + let value = configItem.value; + switch (configItem.type) { + case 'measurement': + value = value + configItem.unit; + break; + case 'color': + value = getColorValue(value, configItem.alpha ?? '100'); + break; + } + return `${configKey}: ${value};`; + }) + .join('\n'); +} + +const getCleanTitle = (title: string): string => { + return title.replace('--mynah-', '').split('-').join(' '); +}; + +const getColorValue = (hex: string, alpha: string): string => { + const realAlpha = parseInt(alpha); + if (realAlpha === 100) { + return hex; + } else { + let hexToUse = hex.length === 4 ? hex[0] + hex.slice(1, 4).repeat(2) : hex; + var r = parseInt(hexToUse.slice(1, 3), 16), + g = parseInt(hexToUse.slice(3, 5), 16), + b = parseInt(hexToUse.slice(5, 7), 16); + + return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + (parseInt(alpha) / 100).toString() + ')'; + } +}; + +const themeInputText = (title: string, configItem: ConfigItem, onValueChange: (value: string) => void): HTMLDivElement => { + const element = document.createElement('div'); + element.classList.add('mynah-ui-example-input'); + element.classList.add(`mynah-ui-example-input-category-${configItem.category ?? 'other'}`); + + element.innerHTML = ` +

${getCleanTitle(title)}

+ ${configItem.description ?? ''}
+ `; + + const inputElement = document.createElement('input'); + inputElement.setAttribute('type', 'text'); + inputElement.setAttribute('value', configItem.value); + inputElement.addEventListener('change', e => { + onValueChange(inputElement.value); + }); + + const inputElementWrapper = document.createElement('div'); + inputElementWrapper.classList.add('mynah-ui-example-input-wrapper'); + inputElementWrapper.insertAdjacentElement('beforeend', inputElement); + + element.insertAdjacentElement('beforeend', inputElementWrapper); + + return element as HTMLDivElement; +}; + +const themeInputMeasurement = (title: string, configItem: ConfigItem, onValueChange: (value: string, unit: string) => void): HTMLDivElement => { + const element = document.createElement('div'); + element.classList.add('mynah-ui-example-input'); + element.classList.add(`mynah-ui-example-input-category-${configItem.category ?? 'other'}`); + + element.innerHTML = ` +

${getCleanTitle(title)}

+ ${configItem.description ?? ''}
+ `; + + const selectElement = document.createElement('select'); + configItem.units?.forEach(unitKey => { + selectElement.insertAdjacentHTML( + 'beforeend', + ` + + ` + ); + }); + selectElement.addEventListener('change', e => { + inputElement.setAttribute('type', selectElement.value === '' ? 'text' : 'number'); + onValueChange(inputElement.value, selectElement.value); + }); + + const inputElement = document.createElement('input'); + inputElement.setAttribute('type', configItem.unit === '' ? 'text' : 'number'); + inputElement.setAttribute('value', configItem.value); + inputElement.addEventListener('change', e => { + onValueChange(inputElement.value, selectElement.value); + }); + + const inputElementWrapper = document.createElement('div'); + inputElementWrapper.classList.add('mynah-ui-example-input-wrapper'); + inputElementWrapper.insertAdjacentElement('beforeend', inputElement); + inputElementWrapper.insertAdjacentElement('beforeend', selectElement); + + element.insertAdjacentElement('beforeend', inputElementWrapper); + + return element as HTMLDivElement; +}; + +const themeInputColor = (title: string, configItem: ConfigItem, onValueChange: (hex: string, alpha: string) => void): HTMLDivElement => { + const element = document.createElement('div'); + element.classList.add('mynah-ui-example-input'); + element.classList.add(`mynah-ui-example-input-category-${configItem.category ?? 'other'}`); + const splittedValue = { + hex: configItem.value, + alpha: configItem.alpha ?? '100', + }; + + element.innerHTML = ` +

${getCleanTitle(title)}

+ ${configItem.description ?? ''}
+ `; + + const alphaSlider = document.createElement('input'); + alphaSlider.setAttribute('type', 'range'); + alphaSlider.setAttribute('min', '0'); + alphaSlider.setAttribute('max', '100'); + alphaSlider.setAttribute('value', splittedValue.alpha ?? '100'); + alphaSlider.addEventListener('change', e => { + onValueChange(inputElement.value, alphaSlider.value); + (inputElementLabelWrapper.querySelector('small[type="range"] > b') as HTMLElement).innerHTML = `${alphaSlider.value}%`; + }); + + const inputElement = document.createElement('input'); + inputElement.setAttribute('type', 'color'); + inputElement.setAttribute('value', splittedValue.hex); + inputElement.addEventListener('change', e => { + onValueChange(inputElement.value, alphaSlider.value); + (inputElementLabelWrapper.querySelector('small[type="color"] > b') as HTMLElement).innerHTML = inputElement.value; + }); + + const inputElementLabelWrapper = document.createElement('div'); + inputElementLabelWrapper.classList.add('mynah-ui-example-input-wrapper'); + inputElementLabelWrapper.insertAdjacentHTML( + 'beforeend', + ` + Color: ${configItem.value} + Alpha: ${configItem.alpha ?? 100}% + ` + ); + + const inputElementWrapper = document.createElement('div'); + inputElementWrapper.classList.add('mynah-ui-example-input-wrapper'); + inputElementWrapper.insertAdjacentElement('beforeend', inputElement); + inputElementWrapper.insertAdjacentElement('beforeend', alphaSlider); + + element.insertAdjacentElement('beforeend', inputElementLabelWrapper); + element.insertAdjacentElement('beforeend', inputElementWrapper); + + return element as HTMLDivElement; +}; + +const download = (filename: string, text: string) => { + var element = document.createElement('a'); + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +}; diff --git a/vendor/mynah-ui/example/tsconfig.json b/vendor/mynah-ui/example/tsconfig.json new file mode 100644 index 00000000..f4291bfc --- /dev/null +++ b/vendor/mynah-ui/example/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "commonjs", + "target": "ES2019", + "lib": ["ES2020", "es5", "es6", "dom"], + "outDir": "out", + "sourceMap": true, + "strict": true, + "resolveJsonModule": true, + "typeRoots": ["./node_modules/@types"], + "strictPropertyInitialization": false, + }, + "exclude": ["build", "out", "node_modules", ".vscode-test"], +} diff --git a/vendor/mynah-ui/example/webpack.config.js b/vendor/mynah-ui/example/webpack.config.js new file mode 100644 index 00000000..887fc2e2 --- /dev/null +++ b/vendor/mynah-ui/example/webpack.config.js @@ -0,0 +1,58 @@ +'use strict'; + +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +/**@type {import('webpack').Configuration}*/ +const config = { + target: 'web', + entry: './src/main.ts', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + library: 'mynahWeb', + libraryTarget: 'var', + devtoolModuleFilenameTemplate: '../[resource-path]', + }, + plugins: [ + new HtmlWebpackPlugin({ + template: 'src/index.html', + }), + ], + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.js'], + }, + experiments: { asyncWebAssembly: true }, + module: { + rules: [ + { test: /\.md$/, use: ['raw-loader'] }, + { + test: /\.scss$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + mode: 'icss', // Enable ICSS (Interoperable CSS) + }, + }, + }, + 'sass-loader', + ], + }, + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader', + }, + ], + }, + ], + }, +}; +module.exports = config; diff --git a/vendor/mynah-ui/jest.config.js b/vendor/mynah-ui/jest.config.js new file mode 100644 index 00000000..6dd15d1d --- /dev/null +++ b/vendor/mynah-ui/jest.config.js @@ -0,0 +1,12 @@ +const jestConfig = { + testEnvironment: 'jsdom', + preset: 'ts-jest', + modulePathIgnorePatterns: ['/ui-tests/'], + moduleNameMapper: { + '\\.svg$': 'jest-svg-transformer', + '^.+\\.(css|less|scss)$': 'babel-jest', + }, + setupFiles: ['/test-config/config.js', 'core-js'], // Polyfill things like structuredClone +}; + +module.exports = jestConfig; diff --git a/vendor/mynah-ui/mynah_ui_guide.md b/vendor/mynah-ui/mynah_ui_guide.md new file mode 100644 index 00000000..e13c2969 --- /dev/null +++ b/vendor/mynah-ui/mynah_ui_guide.md @@ -0,0 +1,521 @@ +# MynahUI GUI Capabilities Guide + +## Overview + +MynahUI is a data and event-driven chat interface library for browsers and webviews. This guide focuses on the interactive GUI capabilities relevant for building tool permission and approval workflows. + +## Core Concepts + +### Chat Items + +Chat items are the fundamental building blocks of the conversation UI. Each chat item is a "card" that can contain various interactive elements. + +**Basic Structure:** +```typescript +interface ChatItem { + type: ChatItemType; // Determines positioning and styling + messageId?: string; // Unique identifier for updates + body?: string; // Markdown content + buttons?: ChatItemButton[]; // Action buttons + formItems?: ChatItemFormItem[]; // Form inputs + fileList?: FileList; // File tree display + followUp?: FollowUpOptions; // Quick action pills + // ... many more options +} +``` + +**Chat Item Types:** +- `ANSWER` / `ANSWER_STREAM` / `CODE_RESULT` → Left-aligned (AI responses) +- `PROMPT` / `SYSTEM_PROMPT` → Right-aligned (user messages) +- `DIRECTIVE` → Transparent, no background + +## Interactive Components + +### 1. Buttons (`ChatItemButton`) + +Buttons are the primary action mechanism for user approval/denial workflows. + +**Interface:** +```typescript +interface ChatItemButton { + id: string; // Unique identifier for the button + text?: string; // Button label + icon?: MynahIcons; // Optional icon + status?: 'main' | 'primary' | 'clear' | 'dimmed-clear' | 'info' | 'success' | 'warning' | 'error'; + keepCardAfterClick?: boolean; // If false, removes card after click + waitMandatoryFormItems?: boolean; // Disables until mandatory form items are filled + disabled?: boolean; + description?: string; // Tooltip text +} +``` + +**Status Colors:** +- `main` - Primary brand color +- `primary` - Accent color +- `success` - Green (for approval actions) +- `error` - Red (for denial/rejection actions) +- `warning` - Yellow/orange +- `info` - Blue +- `clear` - Transparent background + +**Event Handler:** +```typescript +onInBodyButtonClicked: (tabId: string, messageId: string, action: { + id: string; + text?: string; + // ... other button properties +}) => void +``` + +**Example - Approval Buttons:** +```typescript +{ + type: ChatItemType.ANSWER, + messageId: 'tool-approval-123', + body: 'Tool execution request...', + buttons: [ + { + id: 'approve-once', + text: 'Approve', + status: 'primary', + icon: MynahIcons.OK + }, + { + id: 'approve-session', + text: 'Approve for Session', + status: 'success', + icon: MynahIcons.OK_CIRCLED + }, + { + id: 'deny', + text: 'Deny', + status: 'error', + icon: MynahIcons.CANCEL, + keepCardAfterClick: false // Card disappears on denial + } + ] +} +``` + +### 2. Form Items (`ChatItemFormItem`) + +Form items allow collecting structured user input alongside button actions. + +**Available Form Types:** +- `textinput` / `textarea` / `numericinput` / `email` +- `select` (dropdown) +- `radiogroup` / `toggle` +- `checkbox` / `switch` +- `stars` (rating) +- `list` (dynamic list of items) +- `pillbox` (tag/pill input) + +**Common Properties:** +```typescript +interface BaseFormItem { + id: string; // Unique identifier + type: string; // Form type + mandatory?: boolean; // Required field + title?: string; // Label + description?: string; // Help text + tooltip?: string; // Tooltip + value?: string; // Initial/current value + disabled?: boolean; +} +``` + +**Example - Checkbox for "Remember Choice":** +```typescript +formItems: [ + { + type: 'checkbox', + id: 'remember-approval', + label: 'Remember this choice for similar requests', + value: 'false', + tooltip: 'If checked, future requests for this tool will be automatically approved' + } +] +``` + +**Example - Toggle for Options:** +```typescript +formItems: [ + { + type: 'toggle', + id: 'approval-scope', + title: 'Approval Scope', + value: 'once', + options: [ + { value: 'once', label: 'Once', icon: MynahIcons.CHECK }, + { value: 'session', label: 'Session', icon: MynahIcons.STACK }, + { value: 'always', label: 'Always', icon: MynahIcons.OK_CIRCLED } + ] + } +] +``` + +**Event Handlers:** +```typescript +onFormChange: (tabId: string, messageId: string, item: ChatItemFormItem, value: any) => void +``` + +### 3. Content Display Options + +#### Markdown Body + +The `body` field supports full markdown including: +- Headings (`#`, `##`, `###`) +- Code blocks with syntax highlighting +- Inline code +- Links +- Lists (ordered/unordered) +- Blockquotes +- Tables + +**Example - Displaying Tool Parameters:** +```typescript +body: `### Tool Execution Request + +**Tool:** \`read_file\` + +**Parameters:** +\`\`\`json +{ + "file_path": "/Users/niko/src/config.ts", + "offset": 0, + "limit": 100 +} +\`\`\` + +Do you want to allow this tool to execute?` +``` + +#### Custom Renderer + +For complex layouts beyond markdown, use `customRenderer` with HTML markup: + +```typescript +customRenderer: ` +
+

Tool: read_file

+ + + + + + + + + + + + + +
ParameterValue
file_path/Users/niko/src/config.ts
offset0
+
+` +``` + +#### Information Cards + +For hierarchical content with status indicators: + +```typescript +informationCard: { + title: 'Security Notice', + status: { + status: 'warning', + icon: MynahIcons.WARNING, + body: 'This tool will access filesystem resources' + }, + description: 'Review the parameters carefully', + content: { + body: '... detailed information ...' + } +} +``` + +### 4. File Lists + +Display file paths with actions and metadata: + +```typescript +fileList: { + fileTreeTitle: 'Files to be accessed', + filePaths: ['/src/config.ts', '/src/main.ts'], + details: { + '/src/config.ts': { + icon: MynahIcons.FILE, + description: 'Configuration file', + clickable: true + } + }, + actions: { + '/src/config.ts': [ + { + name: 'view-details', + icon: MynahIcons.EYE, + description: 'View file details' + } + ] + } +} +``` + +**Event Handler:** +```typescript +onFileActionClick: (tabId: string, messageId: string, filePath: string, actionName: string) => void +``` + +### 5. Follow-Up Pills + +Quick action buttons displayed as pills: + +```typescript +followUp: { + text: 'Quick actions', + options: [ + { + pillText: 'Approve All', + icon: MynahIcons.OK, + status: 'success', + prompt: 'approve-all' // Can trigger automatic actions + }, + { + pillText: 'Deny All', + icon: MynahIcons.CANCEL, + status: 'error', + prompt: 'deny-all' + } + ] +} +``` + +**Event Handler:** +```typescript +onFollowUpClicked: (tabId: string, messageId: string, followUp: ChatItemAction) => void +``` + +## Card Behavior Options + +### Visual States + +```typescript +{ + status?: 'info' | 'success' | 'warning' | 'error'; // Colors the card border/icon + shimmer?: boolean; // Loading animation + canBeVoted?: boolean; // Show thumbs up/down + canBeDismissed?: boolean; // Show dismiss button + snapToTop?: boolean; // Pin to top of chat + border?: boolean; // Show border + hoverEffect?: boolean; // Highlight on hover +} +``` + +### Layout Options + +```typescript +{ + fullWidth?: boolean; // Stretch to container width + padding?: boolean; // Internal padding + contentHorizontalAlignment?: 'default' | 'center'; +} +``` + +### Card Lifecycle + +```typescript +{ + keepCardAfterClick?: boolean; // On buttons - remove card after click + autoCollapse?: boolean; // Auto-collapse long content +} +``` + +## Updating Chat Items + +Chat items can be updated after creation: + +```typescript +// Add new chat item +mynahUI.addChatItem(tabId, chatItem); + +// Update by message ID +mynahUI.updateChatAnswerWithMessageId(tabId, messageId, updatedChatItem); + +// Update last streaming answer +mynahUI.updateLastChatAnswer(tabId, partialChatItem); +``` + +## Complete Example: Tool Approval Workflow + +```typescript +// 1. Show tool approval request +mynahUI.addChatItem('main-tab', { + type: ChatItemType.ANSWER, + messageId: 'tool-approval-read-file-001', + status: 'warning', + icon: MynahIcons.LOCK, + body: `### Tool Execution Request + +**Tool:** \`read_file\` + +**Description:** Read file contents from the filesystem + +**Parameters:** +\`\`\`json +{ + "file_path": "/Users/nikomat/dev/mynah-ui/src/config.ts", + "offset": 0, + "limit": 2000 +} +\`\`\` + +**Security:** This tool will access local filesystem resources.`, + + formItems: [ + { + type: 'checkbox', + id: 'remember-read-file', + label: 'Trust this tool for the remainder of the session', + value: 'false' + } + ], + + buttons: [ + { + id: 'approve', + text: 'Approve', + status: 'success', + icon: MynahIcons.OK, + keepCardAfterClick: false + }, + { + id: 'deny', + text: 'Deny', + status: 'error', + icon: MynahIcons.CANCEL, + keepCardAfterClick: false + }, + { + id: 'details', + text: 'More Details', + status: 'clear', + icon: MynahIcons.INFO + } + ] +}); + +// 2. Handle button clicks +mynahUI.onInBodyButtonClicked = (tabId, messageId, action) => { + if (messageId === 'tool-approval-read-file-001') { + const formState = mynahUI.getFormState(tabId, messageId); + const rememberChoice = formState['remember-read-file'] === 'true'; + + switch (action.id) { + case 'approve': + // Execute tool + // If rememberChoice, add to session whitelist + break; + case 'deny': + // Cancel tool execution + break; + case 'details': + // Show additional information + mynahUI.updateChatAnswerWithMessageId(tabId, messageId, { + informationCard: { + title: 'Tool Details', + content: { + body: 'Detailed tool documentation...' + } + } + }); + break; + } + } +}; +``` + +## Progressive Updates + +For multi-step approval flows, you can progressively update the same card: + +```typescript +// Initial request +mynahUI.addChatItem(tabId, { + messageId: 'approval-001', + type: ChatItemType.ANSWER, + body: 'Waiting for approval...', + shimmer: true +}); + +// User approves +mynahUI.updateChatAnswerWithMessageId(tabId, 'approval-001', { + body: 'Approved! Executing tool...', + shimmer: true, + buttons: [] // Remove buttons +}); + +// Execution complete +mynahUI.updateChatAnswerWithMessageId(tabId, 'approval-001', { + body: 'Tool execution complete!', + shimmer: false, + status: 'success', + icon: MynahIcons.OK_CIRCLED +}); +``` + +## Sticky Cards + +For persistent approval requests that stay above the prompt: + +```typescript +mynahUI.updateStore(tabId, { + promptInputStickyCard: { + messageId: 'persistent-approval', + body: 'Multiple tools are waiting for approval', + status: 'warning', + icon: MynahIcons.WARNING, + buttons: [ + { + id: 'review-pending', + text: 'Review Pending', + status: 'info' + } + ] + } +}); + +// Clear sticky card +mynahUI.updateStore(tabId, { + promptInputStickyCard: null +}); +``` + +## Best Practices for Tool Approval UI + +1. **Clear Tool Identity**: Always show tool name prominently +2. **Parameter Visibility**: Display all parameters the tool will receive +3. **Security Context**: Indicate security implications (file access, network, etc.) +4. **Action Clarity**: Use clear "Approve" vs "Deny" with appropriate status colors +5. **Scope Options**: Provide "once", "session", "always" choices when appropriate +6. **Non-blocking**: Use `keepCardAfterClick: false` to auto-dismiss after approval +7. **Progressive Disclosure**: Start simple, show details on demand +8. **Feedback**: Update card state to show execution progress after approval + +## Key Event Handlers + +```typescript +interface MynahUIProps { + onInBodyButtonClicked?: (tabId: string, messageId: string, action: ChatItemButton) => void; + onFollowUpClicked?: (tabId: string, messageId: string, followUp: ChatItemAction) => void; + onFormChange?: (tabId: string, messageId: string, item: ChatItemFormItem, value: any) => void; + onFileActionClick?: (tabId: string, messageId: string, filePath: string, actionName: string) => void; + // ... many more +} +``` + +## Reference + +- Full documentation: [mynah-ui/docs/DATAMODEL.md](./docs/DATAMODEL.md) +- Type definitions: [mynah-ui/src/static.ts](./src/static.ts) +- Examples: [mynah-ui/example/src/samples/](./example/src/samples/) diff --git a/vendor/mynah-ui/package-lock.json b/vendor/mynah-ui/package-lock.json new file mode 100644 index 00000000..afcddb25 --- /dev/null +++ b/vendor/mynah-ui/package-lock.json @@ -0,0 +1,10761 @@ +{ + "name": "@aws/mynah-ui", + "version": "4.38.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@aws/mynah-ui", + "version": "4.38.0", + "hasInstallScript": true, + "license": "Apache License 2.0", + "dependencies": { + "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", + "just-clone": "^6.2.0", + "marked": "^14.1.0", + "sanitize-html": "^2.12.1", + "unescape-html": "^1.1.0" + }, + "devDependencies": { + "@babel/core": "^7.23.5", + "@types/eslint": "^8.44.3", + "@types/eslint-scope": "3.7.0", + "@types/estree": "0.0.49", + "@types/glob": "7.1.3", + "@types/jest": "^29.5.5", + "@types/json-schema": "7.0.7", + "@types/minimatch": "^5.1.2", + "@types/node": "^24.0.0", + "@types/sanitize-html": "^2.11.0", + "@typescript-eslint/eslint-plugin": "^5.34.0", + "@typescript-eslint/parser": "^5.62.0", + "babel-jest": "^29.7.0", + "core-js": "^3.33.3", + "css-loader": "6.6.0", + "eslint": "^8.22.0", + "eslint-config-standard-with-typescript": "22.0.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "2.26.0", + "eslint-plugin-n": "15.2.5", + "eslint-plugin-no-null": "^1.0.2", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-promise": "6.0.0", + "husky": "^9.1.6", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-svg-transformer": "^1.0.0", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.3", + "sass": "1.49.8", + "sass-loader": "12.6.0", + "style-loader": "3.3.1", + "svg-url-loader": "^8.0.0", + "ts-jest": "^29.1.1", + "ts-loader": "^9.4.4", + "ts-node": "^10.9.1", + "typedoc": "^0.25.13", + "typescript": "^5.1.6", + "webpack": "5.94.0", + "webpack-cli": "4.7.2" + }, + "peerDependencies": { + "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", + "just-clone": "^6.2.0", + "marked": "^14.1.0", + "sanitize-html": "^2.12.1", + "unescape-html": "^1.1.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.8.tgz", + "integrity": "sha512-KDqYz4PiOWvDFrdHLPhKtCThtIcKVy6avWD2oG4GEvyQ+XDZwHD4YQd+H2vNMnq2rkdxsDkU82T+Vk8U/WXHRQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", + "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/console/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/core/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/reporters/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/transform/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@jest/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.21.tgz", + "integrity": "sha512-SRfKmRe1KvYnxjEMtxEr+J4HIeMX5YBg/qhRHpxEIGjhX1rshcHlnFUE9K0GazhVKWM7B+nARSkV8LuvJdJ5/g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.2.tgz", + "integrity": "sha512-uQDwm1wFHmbBbCZCqAlq6Do9LYwByNZHWzXppSnay9SuwJ+VRbjkbLABer54kcPnMSlG6Fdiy2yaFXm/z9Z5gw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", + "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.49", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.49.tgz", + "integrity": "sha512-K1AFuMe8a+pXmfHTtnwBvqoEylNKVeaiKYkjmcEAdytMQVJ/i9Fu7sc13GxgXdO49gkE7Hy8SyJonUZUn+eVaw==", + "dev": true + }, + "node_modules/@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.11", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.11.tgz", + "integrity": "sha512-S2mHmYIVe13vrm6q4kN6fLYYAka15ALQki/vgDC3mIukEOx8WJlv0kQPM+d4w8Gp6u0uSdKND04IlTXBv0rwnQ==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "24.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz", + "integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/sanitize-html": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.11.0.tgz", + "integrity": "sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==", + "dev": true, + "dependencies": { + "htmlparser2": "^8.0.0" + } + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz", + "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==", + "dev": true, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x", + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz", + "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==", + "dev": true, + "dependencies": { + "envinfo": "^7.7.3" + }, + "peerDependencies": { + "webpack-cli": "4.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz", + "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==", + "dev": true, + "peerDependencies": { + "webpack-cli": "4.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-sequence-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", + "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", + "dev": true + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/babel-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/builtins/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/builtins/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/builtins/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001579", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001579.tgz", + "integrity": "sha512-u5AUVkixruKHJjw/pj9wISlcMpgFWzSrczLZbrqBSxukQixmg0SJ5sZTpvaFvxU0HoQKd4yoyAogyrAz9pzJnA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/core-js": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/create-jest/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/create-jest/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.6.0.tgz", + "integrity": "sha512-FK7H2lisOixPT406s5gZM1S3l8GrfhEBT3ZiL2UX1Ng1XWs0y2GPllz/OTyvbaHe12VgQrIXIzuEGVlbUhodqg==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.5", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.637", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.637.tgz", + "integrity": "sha512-G7j3UCOukFtxVO1vWrPQUoDk3kL70mtvjc/DC/k2o7lE0wAdq+Vwp1ipagOow+BH0uVztFysLWbkM/RTIrbK3w==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", + "integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-standard": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.0.0.tgz", + "integrity": "sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peerDependencies": { + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0" + } + }, + "node_modules/eslint-config-standard-with-typescript": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-22.0.0.tgz", + "integrity": "sha512-VA36U7UlFpwULvkdnh6MQj5GAV2Q+tT68ALLAwJP0ZuNXU2m0wX07uxX4qyLRdHgSzH4QJ73CveKBuSOYvh7vQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "typescript": "*" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-es": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz", + "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-header": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz", + "integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==", + "dev": true, + "peerDependencies": { + "eslint": ">=7.7.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-n": { + "version": "15.2.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.2.5.tgz", + "integrity": "sha512-8+BYsqiyZfpu6NXmdLOXVUfk8IocpCjpd8nMRRH0A9ulrcemhb2VI9RSJMEy5udx++A/YcVPD11zT8hpFq368g==", + "dev": true, + "dependencies": { + "builtins": "^5.0.1", + "eslint-plugin-es": "^4.1.0", + "eslint-utils": "^3.0.0", + "ignore": "^5.1.1", + "is-core-module": "^2.10.0", + "minimatch": "^3.1.2", + "resolve": "^1.22.1", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-n/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-n/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-n/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/eslint-plugin-no-null": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-null/-/eslint-plugin-no-null-1.0.2.tgz", + "integrity": "sha512-uRDiz88zCO/2rzGfgG15DBjNsgwWtWiSo4Ezy7zzajUgpnFIqd1TjepKeRmJZHEfBGu58o2a8S0D7vglvvhkVA==", + "dev": true, + "engines": { + "node": ">=5.0.0" + }, + "peerDependencies": { + "eslint": ">=3.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-promise": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.0.0.tgz", + "integrity": "sha512-7GPezalm5Bfi/E22PnQxDWH2iW9GTvAlUNTztemeHb6c1BniSyoeTrM87JkC0wYdi6aQrZX9p2qEiAno8aTcbw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", + "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.0.tgz", + "integrity": "sha512-6ErL7JlGu2CNFHyRQEuDogOyGPNiqcuWdt4iSSFUPyferNTGlNTPFqeV36Y/XwA4V/TJ8l0sxp6FTnxud/mf8g==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/husky": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-circus/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-config/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-diff/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-each/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-matcher-utils/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-message-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-resolve/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runner/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-runtime/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-snapshot/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-svg-transformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jest-svg-transformer/-/jest-svg-transformer-1.0.0.tgz", + "integrity": "sha512-+kD21VthJFHIbI3DZRz+jo4sBOSR1qWEMXhVC28owRMqC5nA+zEiJrHOlj+EqQIztYMouRc1dIjE8SJfFPJUXA==", + "dev": true, + "peerDependencies": { + "jest": ">22", + "react": ">=16" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-util/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-validate/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/jest-watcher/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/just-clone": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz", + "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", + "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==", + "dev": true + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/marked": { + "version": "14.1.4", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz", + "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/cross-spawn": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/npm-run-all/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/npm-run-all/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "dev": true + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz", + "integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "dev": true, + "dependencies": { + "resolve": "^1.9.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sanitize-html": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", + "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/sanitize-html/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sanitize-html/node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sass": { + "version": "1.49.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz", + "integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/sass-loader": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz", + "integrity": "sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA==", + "dev": true, + "dependencies": { + "klona": "^2.0.4", + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shiki": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.7.tgz", + "integrity": "sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==", + "dev": true, + "dependencies": { + "ansi-sequence-parser": "^1.1.0", + "jsonc-parser": "^3.2.0", + "vscode-oniguruma": "^1.7.0", + "vscode-textmate": "^8.0.0" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", + "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", + "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-url-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/svg-url-loader/-/svg-url-loader-8.0.0.tgz", + "integrity": "sha512-5doSXvl18hY1fGsRLdhWAU5jgzgxJ06/gc/26cpuDnN0xOz1HmmfhkpL29SSrdIvhtxQ1UwGzmk7wTT/l48mKw==", + "dev": true, + "dependencies": { + "file-loader": "~6.2.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.27.0.tgz", + "integrity": "sha512-bi1HRwVRskAjheeYl291n3JC4GgO/Ty4z1nVs5AAsmonJulGxpSektecnNedrwK9C7vpvVtcX3cw00VSLt7U2A==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typedoc": { + "version": "0.25.13", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.25.13.tgz", + "integrity": "sha512-pQqiwiJ+Z4pigfOnnysObszLiU3mVLWAExSPf+Mu06G/qsc3wzbuM56SZQvONhHLncLUhYzOVkjFFpFfL5AzhQ==", + "dev": true, + "dependencies": { + "lunr": "^2.3.9", + "marked": "^4.3.0", + "minimatch": "^9.0.3", + "shiki": "^0.14.7" + }, + "bin": { + "typedoc": "bin/typedoc" + }, + "engines": { + "node": ">= 16" + }, + "peerDependencies": { + "typescript": "4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x" + } + }, + "node_modules/typedoc/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typedoc/node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/typedoc/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unescape-html": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unescape-html/-/unescape-html-1.1.0.tgz", + "integrity": "sha512-O9/yBNqIkArjS597iHez5hAaAdn7b8/230SX8IncgXAX5tWI9XlEQYaz6Qbou0Sloa9n6lx9G5s6hg5qhJyzGg==" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/v8-compile-cache": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", + "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vscode-oniguruma": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/vscode-oniguruma/-/vscode-oniguruma-1.7.0.tgz", + "integrity": "sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==", + "dev": true + }, + "node_modules/vscode-textmate": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/vscode-textmate/-/vscode-textmate-8.0.0.tgz", + "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", + "dev": true + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/webpack": { + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.2.tgz", + "integrity": "sha512-mEoLmnmOIZQNiRl0ebnjzQ74Hk0iKS5SiEEnpq3dRezoyR3yPaeQZCMCe+db4524pj1Pd5ghZXjT41KLzIhSLw==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.0.4", + "@webpack-cli/info": "^1.3.0", + "@webpack-cli/serve": "^1.5.1", + "colorette": "^1.2.1", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "v8-compile-cache": "^2.2.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "webpack": "4.x.x || 5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "@webpack-cli/migrate": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/vendor/mynah-ui/package.json b/vendor/mynah-ui/package.json new file mode 100644 index 00000000..a7edf61c --- /dev/null +++ b/vendor/mynah-ui/package.json @@ -0,0 +1,110 @@ +{ + "name": "@aws/mynah-ui", + "displayName": "AWS Mynah UI", + "version": "4.38.0", + "description": "AWS Toolkit VSCode and Intellij IDE Extension Mynah UI", + "publisher": "Amazon Web Services", + "license": "Apache License 2.0", + "readme": "README.md", + "main": "./dist/main.js", + "repository": { + "type": "git", + "url": "https://github.com/aws/mynah-ui" + }, + "scripts": { + "clean": "npm run clean:dist && npm run clean:node", + "clean:dist": "find . -name 'dist' -type d -prune -exec rm -rf '{}' +", + "clean:node": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", + "build": "webpack --config webpack.config.js --mode production", + "packdemo": "cd ./example && npm run pack", + "watch": "webpack --config webpack.config.js --mode development --watch", + "watch:example": "cd ./example && npm run watch", + "watch:web": "run-p watch watch:example serve:example", + "serve:example": "live-server --port=9000 example/dist", + "start:example": "cd ./example && npm install && npm run build && cd .. && npm run watch:web", + "dev": "npm ci && npm run build && cd ./ui-tests && npm install && cd .. && npm run start:example", + "lint-fix": "npx eslint \"./**\" --fix", + "lint": "npx eslint \"./**\"", + "format:check": "npx prettier --check .", + "format:write": "npx prettier --write .", + "docker:clean": "docker rm -f mynah-ui-e2e-container || true", + "docker:build": "node scripts/docker-build.js", + "docker:run": "docker run --name mynah-ui-e2e-container -e WEBKIT_FORCE_COMPLEX_TEXT=0 -e WEBKIT_DISABLE_COMPOSITING_MODE=1 mynah-ui-e2e", + "docker:run:chromium": "docker run -e BROWSER=chromium --name mynah-ui-e2e-container mynah-ui-e2e", + "docker:run:webkit": "docker run -e BROWSER=webkit -e WEBKIT_FORCE_COMPLEX_TEXT=0 -e WEBKIT_DISABLE_COMPOSITING_MODE=1 --name mynah-ui-e2e-container mynah-ui-e2e", + "docker:extract": "docker cp mynah-ui-e2e-container:/app/ui-tests/__results__ ./ui-tests/ && docker cp mynah-ui-e2e-container:/app/ui-tests/__snapshots__ ./ui-tests/__results__/__snapshots__", + "playwright:setup": "node scripts/setup-playwright.js", + "playwright:version": "node scripts/get-playwright-version.js", + "playwright:pre-test": "node scripts/pre-test-setup.js", + "tests:e2e": "npm run docker:clean && npm run docker:build && npm run docker:run", + "tests:e2e:chromium": "npm run docker:clean && npm run docker:build && npm run docker:run:chromium", + "tests:e2e:webkit": "npm run docker:clean && npm run docker:build && npm run docker:run:webkit", + "tests:e2e:webkit:local": "npm run playwright:pre-test && cd ui-tests && npm run e2e:webkit", + "tests:webkit:check": "node scripts/test-webkit.js", + "tests:e2e:local": "npm run playwright:pre-test && cd ui-tests && npm run e2e", + "tests:e2e:trace": "cd ./ui-tests && npm run trace", + "tests:unit": "jest --collect-coverage", + "api-docs": "npx typedoc src/main.ts --out ./api-docs", + "api-doc-deploy": "npx typedoc src/main.ts --out ./example/dist/api-doc", + "postinstall": "node postinstall.js", + "prepare": "npx husky" + }, + "dependencies": { + "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", + "just-clone": "^6.2.0", + "marked": "^14.1.0", + "sanitize-html": "^2.12.1", + "unescape-html": "^1.1.0" + }, + "peerDependencies": { + "escape-html": "^1.0.3", + "highlight.js": "^11.11.0", + "just-clone": "^6.2.0", + "marked": "^14.1.0", + "sanitize-html": "^2.12.1", + "unescape-html": "^1.1.0" + }, + "devDependencies": { + "@babel/core": "^7.23.5", + "@types/eslint": "^8.44.3", + "@types/eslint-scope": "3.7.0", + "@types/estree": "0.0.49", + "@types/glob": "7.1.3", + "@types/jest": "^29.5.5", + "@types/json-schema": "7.0.7", + "@types/minimatch": "^5.1.2", + "@types/node": "^24.0.0", + "@types/sanitize-html": "^2.11.0", + "@typescript-eslint/eslint-plugin": "^5.34.0", + "@typescript-eslint/parser": "^5.62.0", + "babel-jest": "^29.7.0", + "core-js": "^3.33.3", + "css-loader": "6.6.0", + "eslint": "^8.22.0", + "eslint-config-standard-with-typescript": "22.0.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "2.26.0", + "eslint-plugin-n": "15.2.5", + "eslint-plugin-no-null": "^1.0.2", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-promise": "6.0.0", + "husky": "^9.1.6", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-svg-transformer": "^1.0.0", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.3", + "sass": "1.49.8", + "sass-loader": "12.6.0", + "style-loader": "3.3.1", + "svg-url-loader": "^8.0.0", + "ts-jest": "^29.1.1", + "ts-loader": "^9.4.4", + "ts-node": "^10.9.1", + "typedoc": "^0.25.13", + "typescript": "^5.1.6", + "webpack": "5.94.0", + "webpack-cli": "4.7.2" + } +} diff --git a/vendor/mynah-ui/postinstall.js b/vendor/mynah-ui/postinstall.js new file mode 100644 index 00000000..353deef8 --- /dev/null +++ b/vendor/mynah-ui/postinstall.js @@ -0,0 +1,9 @@ +const deprecationList = [ + 'Config.texts.clickFileToViewDiff will be deprecated after 5.x.x', + 'MynahUIProps.onOpenDiff will be deprecated after 5.x.x', + 'ChatItemContent.buttons will render in the order of the array starting from v5.0.0, instead of reverse order.', + 'MynahUIProps.onCodeInsertToCursorPosition will be deprecated after 5.x.x', + 'MynahUIProps.onCopyCodeToClipboard will be changed to be used only on keyboard and context menu copy actions after 5.x.x', +]; + +deprecationList.forEach((deprecationItem) => console.log(deprecationItem)); diff --git a/vendor/mynah-ui/scripts/docker-build.js b/vendor/mynah-ui/scripts/docker-build.js new file mode 100755 index 00000000..38acc8c1 --- /dev/null +++ b/vendor/mynah-ui/scripts/docker-build.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +/** + * Script to build Docker image with detected Playwright version + */ + +const { execSync } = require('child_process'); +const { getPlaywrightVersion } = require('./get-playwright-version'); + +function buildDockerImage() { + try { + const version = getPlaywrightVersion(); + console.log(`Building Docker image with Playwright version: ${version}`); + + // Use the detected version or fallback to latest + // Add 'v' prefix for version numbers, but not for 'latest' + const dockerVersion = version === 'latest' ? 'latest' : `v${version}`; + + const buildCommand = `docker build --build-arg PLAYWRIGHT_VERSION=${dockerVersion} -t mynah-ui-e2e .`; + + console.log(`Executing: ${buildCommand}`); + execSync(buildCommand, { stdio: 'inherit' }); + + console.log('Docker image built successfully!'); + } catch (error) { + console.error('Error building Docker image:', error.message); + process.exit(1); + } +} + +// If called directly, run the build +if (require.main === module) { + buildDockerImage(); +} + +module.exports = { buildDockerImage }; diff --git a/vendor/mynah-ui/scripts/docker-health-check.js b/vendor/mynah-ui/scripts/docker-health-check.js new file mode 100644 index 00000000..9f8864f3 --- /dev/null +++ b/vendor/mynah-ui/scripts/docker-health-check.js @@ -0,0 +1,45 @@ +#!/usr/bin/env node + +/** + * Docker health check script to verify browser installations + */ + +const { execSync } = require('child_process'); + +function healthCheck() { + try { + console.log('=== Docker Health Check ==='); + + // Check if we're in Docker + console.log('Environment: Docker Container'); + + // Check Playwright installation + console.log('\n1. Checking Playwright...'); + execSync('npx playwright --version', { stdio: 'inherit' }); + + // Check browser installations + console.log('\n2. Checking browsers...'); + execSync('npx playwright install --dry-run', { stdio: 'inherit' }); + + // Test WebKit specifically + console.log('\n3. Testing WebKit launch...'); + execSync('npx playwright test --list --project=webkit | head -5', { + stdio: 'inherit', + shell: true, + }); + + console.log('\n=== Health Check Passed ==='); + return true; + } catch (error) { + console.error('Health check failed:', error.message); + return false; + } +} + +// If called directly, run the health check +if (require.main === module) { + const success = healthCheck(); + process.exit(success ? 0 : 1); +} + +module.exports = { healthCheck }; diff --git a/vendor/mynah-ui/scripts/get-playwright-version.js b/vendor/mynah-ui/scripts/get-playwright-version.js new file mode 100755 index 00000000..aafcbcd0 --- /dev/null +++ b/vendor/mynah-ui/scripts/get-playwright-version.js @@ -0,0 +1,62 @@ +#!/usr/bin/env node + +/** + * Script to detect and return the appropriate Playwright version + * Priority: local installation > package.json > latest + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +function getPlaywrightVersion() { + try { + // 1. Try to get locally installed version + try { + const localVersion = execSync('playwright --version', { encoding: 'utf8', stdio: 'pipe' }); + const versionMatch = localVersion.match(/Version (\d+\.\d+\.\d+)/); + if (versionMatch) { + console.log(`Found local Playwright version: ${versionMatch[1]}`); + return versionMatch[1]; + } + } catch (error) { + console.log('No local Playwright installation found'); + } + + // 2. Try to get version from ui-tests package.json + const uiTestsPackagePath = path.join(__dirname, '../ui-tests/package.json'); + if (fs.existsSync(uiTestsPackagePath)) { + const packageJson = JSON.parse(fs.readFileSync(uiTestsPackagePath, 'utf8')); + + // Check both playwright and @playwright/test dependencies + const playwrightVersion = packageJson.devDependencies?.playwright || packageJson.dependencies?.playwright; + const playwrightTestVersion = + packageJson.devDependencies?.['@playwright/test'] || packageJson.dependencies?.['@playwright/test']; + + // Prefer @playwright/test version if available, otherwise use playwright + const version = playwrightTestVersion || playwrightVersion; + + if (version) { + // Remove ^ or ~ prefix and get clean version + const cleanVersion = version.replace(/[\^~]/, ''); + const sourcePackage = playwrightTestVersion ? '@playwright/test' : 'playwright'; + console.log(`Found ${sourcePackage} version in ui-tests package.json: ${cleanVersion}`); + return cleanVersion; + } + } + + // 3. Fallback to latest + console.log('No specific version found, using latest'); + return 'latest'; + } catch (error) { + console.error('Error detecting Playwright version:', error.message); + return 'latest'; + } +} + +// If called directly, output the version +if (require.main === module) { + console.log(getPlaywrightVersion()); +} + +module.exports = { getPlaywrightVersion }; diff --git a/vendor/mynah-ui/scripts/pre-test-setup.js b/vendor/mynah-ui/scripts/pre-test-setup.js new file mode 100755 index 00000000..95afb63b --- /dev/null +++ b/vendor/mynah-ui/scripts/pre-test-setup.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node + +/** + * Pre-test setup script that ensures Playwright is properly configured + * This runs before tests to guarantee version consistency + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); +const { getPlaywrightVersion } = require('./get-playwright-version'); + +function checkPlaywrightInstallation() { + const uiTestsPath = path.join(__dirname, '../ui-tests'); + const nodeModulesPath = path.join(uiTestsPath, 'node_modules'); + const playwrightPath = path.join(nodeModulesPath, '@playwright'); + + return fs.existsSync(playwrightPath); +} + +function getInstalledPlaywrightVersion() { + try { + const uiTestsPath = path.join(__dirname, '../ui-tests'); + const packageLockPath = path.join(uiTestsPath, 'package-lock.json'); + + if (fs.existsSync(packageLockPath)) { + const packageLock = JSON.parse(fs.readFileSync(packageLockPath, 'utf8')); + return ( + packageLock.packages?.['node_modules/@playwright/test']?.version || + packageLock.packages?.['node_modules/playwright']?.version || + null + ); + } + return null; + } catch (error) { + console.warn('Could not read package-lock.json:', error.message); + return null; + } +} + +function preTestSetup() { + console.log('🔍 Running pre-test setup...'); + + try { + const expectedVersion = getPlaywrightVersion(); + console.log(`📋 Expected Playwright version: ${expectedVersion}`); + + const isInstalled = checkPlaywrightInstallation(); + const installedVersion = getInstalledPlaywrightVersion(); + + console.log(`📦 Playwright installed: ${isInstalled}`); + console.log(`📦 Installed version: ${installedVersion || 'unknown'}`); + + // Check if we need to install/update + const needsSetup = !isInstalled || (expectedVersion !== 'latest' && installedVersion !== expectedVersion); + + if (needsSetup) { + console.log('🔧 Setting up Playwright...'); + + // Run setup with target directory + const { setupPlaywright } = require('./setup-playwright'); + const uiTestsPath = path.join(__dirname, '../ui-tests'); + setupPlaywright(uiTestsPath); + } else { + console.log('✅ Playwright is already properly configured'); + } + + console.log('🎉 Pre-test setup completed successfully!'); + } catch (error) { + console.error('❌ Pre-test setup failed:', error.message); + process.exit(1); + } +} + +// If called directly, run the setup +if (require.main === module) { + preTestSetup(); +} + +module.exports = { preTestSetup }; diff --git a/vendor/mynah-ui/scripts/setup-playwright.js b/vendor/mynah-ui/scripts/setup-playwright.js new file mode 100755 index 00000000..ddfe885f --- /dev/null +++ b/vendor/mynah-ui/scripts/setup-playwright.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +/** + * Script to setup Playwright with version-agnostic approach + * This ensures consistent versions across local and Docker environments + */ + +const { execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); +const { getPlaywrightVersion } = require('./get-playwright-version'); + +function setupPlaywright(targetDir = null) { + try { + const version = getPlaywrightVersion(); + console.log(`Setting up Playwright version: ${version}`); + + // Determine target directory + const uiTestsPath = targetDir || path.join(__dirname, '../ui-tests'); + + // Ensure target directory exists + if (!fs.existsSync(uiTestsPath)) { + throw new Error(`Target directory does not exist: ${uiTestsPath}`); + } + + const installCommand = + version === 'latest' + ? 'npm install @playwright/test@latest playwright@latest --save-dev' + : `npm install @playwright/test@${version} playwright@${version} --save-dev`; + + console.log(`Installing Playwright in ${uiTestsPath}...`); + execSync(installCommand, { + stdio: 'inherit', + cwd: uiTestsPath, + }); + + // Install Playwright browsers with dependencies + console.log('Installing Playwright browsers with dependencies...'); + execSync('npx playwright install --with-deps', { + stdio: 'inherit', + cwd: uiTestsPath, + }); + + console.log('Playwright setup completed successfully!'); + return true; + } catch (error) { + console.error('Error setting up Playwright:', error.message); + throw error; // Re-throw to allow caller to handle + } +} + +// If called directly, run the setup +if (require.main === module) { + try { + setupPlaywright(); + } catch (error) { + process.exit(1); + } +} + +module.exports = { setupPlaywright }; diff --git a/vendor/mynah-ui/scripts/test-webkit.js b/vendor/mynah-ui/scripts/test-webkit.js new file mode 100644 index 00000000..eed2a940 --- /dev/null +++ b/vendor/mynah-ui/scripts/test-webkit.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +/** + * Script to test WebKit browser availability and functionality + */ + +const { execSync } = require('child_process'); +const path = require('path'); + +function testWebKit() { + try { + console.log('Testing WebKit browser availability...'); + + const uiTestsPath = path.join(__dirname, '../ui-tests'); + + // Check if WebKit is installed + console.log('Checking WebKit installation...'); + execSync('npx playwright install webkit --with-deps', { + stdio: 'inherit', + cwd: uiTestsPath, + }); + + // Run a simple WebKit test + console.log('Running WebKit test...'); + execSync('npx playwright test --project=webkit --grep "should render initial data"', { + stdio: 'inherit', + cwd: uiTestsPath, + }); + + console.log('WebKit test completed successfully!'); + return true; + } catch (error) { + console.error('WebKit test failed:', error.message); + return false; + } +} + +// If called directly, run the test +if (require.main === module) { + const success = testWebKit(); + process.exit(success ? 0 : 1); +} + +module.exports = { testWebKit }; diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/chat-item-relevance-vote-coverage.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/chat-item-relevance-vote-coverage.spec.ts new file mode 100644 index 00000000..4492383e --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/chat-item-relevance-vote-coverage.spec.ts @@ -0,0 +1,60 @@ +import { ChatItemRelevanceVote } from '../../../components/chat-item/chat-item-relevance-vote'; +import { MynahEventNames, RelevancyVoteType } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { Config } from '../../../helper/config'; +import testIds from '../../../helper/test-ids'; + +jest.mock('../../../helper/config'); +jest.mock('../../../helper/events'); + +describe('ChatItemRelevanceVote Coverage Tests', () => { + let relevanceVote: ChatItemRelevanceVote; + let mockGlobalEvents: jest.Mocked; + + beforeEach(() => { + document.body.innerHTML = ''; + + (Config.getInstance as jest.Mock).mockReturnValue({ + config: { + texts: { + feedbackThanks: 'Thank you!', + feedbackReportButtonLabel: 'Report' + }, + componentClasses: {}, + test: true + } + }); + + mockGlobalEvents = { + dispatch: jest.fn(), + addListener: jest.fn().mockReturnValue('listener-id'), + removeListener: jest.fn() + } as any; + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue(mockGlobalEvents); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should handle downvote', () => { + relevanceVote = new ChatItemRelevanceVote({ + tabId: 'tab-1', + messageId: 'msg-1' + }); + document.body.appendChild(relevanceVote.render); + + const downvoteInput = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.downvote}"]`) as HTMLInputElement; + downvoteInput.checked = true; + downvoteInput.dispatchEvent(new Event('change')); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CARD_VOTE, + { + messageId: 'msg-1', + tabId: 'tab-1', + vote: RelevancyVoteType.DOWN + } + ); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/chat-item-relevance-vote.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/chat-item-relevance-vote.spec.ts new file mode 100644 index 00000000..4a2a1d6e --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/chat-item-relevance-vote.spec.ts @@ -0,0 +1,178 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItemRelevanceVote, ChatItemRelevanceVoteProps } from '../../../components/chat-item/chat-item-relevance-vote'; +import { MynahEventNames, RelevancyVoteType } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { Config } from '../../../helper/config'; +import testIds from '../../../helper/test-ids'; + +// Mock dependencies +jest.mock('../../../helper/config'); +jest.mock('../../../helper/events'); + +describe('ChatItemRelevanceVote Component', () => { + let relevanceVote: ChatItemRelevanceVote; + let mockConfig: jest.Mocked; + let mockGlobalEvents: jest.Mocked; + + const mockTexts = { + feedbackThanks: 'Thank you for your feedback!', + feedbackReportButtonLabel: 'Report an issue' + }; + + const defaultProps: ChatItemRelevanceVoteProps = { + tabId: 'test-tab-123', + messageId: 'test-message-456', + classNames: [ 'custom-class' ] + }; + + beforeEach(() => { + document.body.innerHTML = ''; + + // Setup Config mock + mockConfig = { + config: { + texts: mockTexts, + componentClasses: {}, + test: true + } + } as any; + (Config.getInstance as jest.Mock).mockReturnValue(mockConfig); + + // Setup GlobalEvents mock + mockGlobalEvents = { + dispatch: jest.fn(), + addListener: jest.fn().mockReturnValue('listener-id-123'), + removeListener: jest.fn() + } as any; + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue(mockGlobalEvents); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + jest.clearAllTimers(); + }); + + describe('Constructor', () => { + it('should create relevance vote component with required props', () => { + const minimalProps: ChatItemRelevanceVoteProps = { + tabId: 'tab-1', + messageId: 'msg-1' + }; + + relevanceVote = new ChatItemRelevanceVote(minimalProps); + + expect(relevanceVote).toBeDefined(); + expect(relevanceVote.props).toEqual(minimalProps); + expect(relevanceVote.render).toBeDefined(); + }); + + it('should handle undefined classNames', () => { + const propsWithoutClassNames: ChatItemRelevanceVoteProps = { + tabId: 'tab-1', + messageId: 'msg-1' + }; + + relevanceVote = new ChatItemRelevanceVote(propsWithoutClassNames); + + expect(relevanceVote).toBeDefined(); + expect(relevanceVote.render.classList.contains('mynah-card-votes-wrapper')).toBe(true); + }); + }); + + describe('Upvote Functionality', () => { + beforeEach(() => { + relevanceVote = new ChatItemRelevanceVote(defaultProps); + document.body.appendChild(relevanceVote.render); + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should handle upvote change event', () => { + const upvoteInput = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.upvote}"]`) as HTMLInputElement; + + upvoteInput.checked = true; + upvoteInput.dispatchEvent(new Event('change')); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CARD_VOTE, + { + messageId: 'test-message-456', + tabId: 'test-tab-123', + vote: RelevancyVoteType.UP + } + ); + }); + + it('should replace content with thanks message after upvote', () => { + const upvoteInput = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.upvote}"]`) as HTMLInputElement; + + upvoteInput.checked = true; + upvoteInput.dispatchEvent(new Event('change')); + + const thanksElement = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.thanks}"]`); + expect(thanksElement).toBeDefined(); + expect(thanksElement?.innerHTML).toBe('Thank you for your feedback!'); + }); + + it('should remove component after timeout for upvote', () => { + const removeSpy = jest.spyOn(relevanceVote.render, 'remove'); + const upvoteInput = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.upvote}"]`) as HTMLInputElement; + + upvoteInput.checked = true; + upvoteInput.dispatchEvent(new Event('change')); + + expect(removeSpy).not.toHaveBeenCalled(); + + // Fast-forward time by 3500ms + jest.advanceTimersByTime(3500); + + expect(removeSpy).toHaveBeenCalled(); + }); + }); + + describe('Downvote Functionality', () => { + beforeEach(() => { + relevanceVote = new ChatItemRelevanceVote(defaultProps); + document.body.appendChild(relevanceVote.render); + }); + + it('should handle downvote change event', () => { + const downvoteInput = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.downvote}"]`) as HTMLInputElement; + + downvoteInput.checked = true; + downvoteInput.dispatchEvent(new Event('change')); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CARD_VOTE, + { + messageId: 'test-message-456', + tabId: 'test-tab-123', + vote: RelevancyVoteType.DOWN + } + ); + }); + + it('should replace content with thanks message and report button after downvote', () => { + const downvoteInput = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.downvote}"]`) as HTMLInputElement; + + downvoteInput.checked = true; + downvoteInput.dispatchEvent(new Event('change')); + + const thanksElement = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.thanks}"]`); + const reportButton = relevanceVote.render.querySelector(`[data-testid="${testIds.chatItem.vote.reportButton}"]`); + + expect(thanksElement).toBeDefined(); + expect(thanksElement?.innerHTML).toBe('Thank you for your feedback!'); + expect(reportButton).toBeDefined(); + expect(reportButton?.textContent).toBe('Report an issue'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-edge-cases.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-edge-cases.spec.ts new file mode 100644 index 00000000..368107b1 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-edge-cases.spec.ts @@ -0,0 +1,327 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PromptTopBar } from '../../../../../components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar'; +import { QuickActionCommand } from '../../../../../static'; +import { MynahIcons } from '../../../../../components/icon'; + +// Mock the global events +jest.mock('../../../../../helper/events', () => ({ + MynahUIGlobalEvents: { + getInstance: jest.fn(() => ({ + addListener: jest.fn(), + dispatch: jest.fn() + })) + }, + cancelEvent: jest.fn() +})); + +// Mock the overlay component +jest.mock('../../../../../components/overlay', () => { + const mockClose = jest.fn(); + const mockUpdateContent = jest.fn(); + const mockOverlay = jest.fn().mockImplementation(() => ({ + close: mockClose, + updateContent: mockUpdateContent + })); + + return { + Overlay: mockOverlay, + OverlayHorizontalDirection: { + START_TO_RIGHT: 'start-to-right', + END_TO_LEFT: 'end-to-left' + }, + OverlayVerticalDirection: { + TO_TOP: 'to-top' + } + }; +}); + +describe('PromptTopBar Edge Cases', () => { + let promptTopBar: PromptTopBar; + + const basicContextItems: QuickActionCommand[] = [ + { + id: 'context-1', + command: '@file1', + description: 'First context item', + icon: MynahIcons.FILE + }, + { + id: 'context-2', + command: '@file2', + description: 'Second context item', + icon: MynahIcons.FILE + } + ]; + + beforeEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + jest.useFakeTimers(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + describe('Context Tooltip', () => { + it('should show tooltip with icon when context item has icon', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill') as HTMLElement; + + // Create a proper mouse event with target + const mouseEvent = new MouseEvent('mouseenter'); + Object.defineProperty(mouseEvent, 'target', { + value: contextPill, + enumerable: true + }); + + (promptTopBar as any).showContextTooltip(mouseEvent, basicContextItems[0]); + + // Fast-forward timer + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should show tooltip without icon when context item has no icon', () => { + const contextItemWithoutIcon: QuickActionCommand = { + id: 'no-icon', + command: '@noicon', + description: 'No icon item' + }; + + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ contextItemWithoutIcon ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill') as HTMLElement; + + const mouseEvent = new MouseEvent('mouseenter'); + Object.defineProperty(mouseEvent, 'target', { + value: contextPill, + enumerable: true + }); + + (promptTopBar as any).showContextTooltip(mouseEvent, contextItemWithoutIcon); + + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should show tooltip without description when context item has no description', () => { + const contextItemWithoutDescription: QuickActionCommand = { + id: 'no-desc', + command: '@nodesc', + icon: MynahIcons.FILE + }; + + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ contextItemWithoutDescription ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill') as HTMLElement; + + const mouseEvent = new MouseEvent('mouseenter'); + Object.defineProperty(mouseEvent, 'target', { + value: contextPill, + enumerable: true + }); + + (promptTopBar as any).showContextTooltip(mouseEvent, contextItemWithoutDescription); + + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should clear existing tooltip timeout when showing new tooltip', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill') as HTMLElement; + + const mouseEvent = new MouseEvent('mouseenter'); + Object.defineProperty(mouseEvent, 'target', { + value: contextPill, + enumerable: true + }); + + // Set up a tooltip timeout + (promptTopBar as any).contextTooltipTimeout = setTimeout(() => {}, 1000); + const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); + + // Show tooltip + (promptTopBar as any).showContextTooltip(mouseEvent, basicContextItems[0]); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + }); + + it('should hide tooltip and clear timeout', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + + // Set up a tooltip timeout + (promptTopBar as any).contextTooltipTimeout = setTimeout(() => {}, 1000); + const clearTimeoutSpy = jest.spyOn(global, 'clearTimeout'); + + // Create a mock tooltip + const mockTooltip = { close: jest.fn() }; + (promptTopBar as any).contextTooltip = mockTooltip; + + (promptTopBar as any).hideContextTooltip(); + + expect(clearTimeoutSpy).toHaveBeenCalled(); + expect(mockTooltip.close).toHaveBeenCalled(); + expect((promptTopBar as any).contextTooltip).toBeNull(); + }); + }); + + describe('Responsive Behavior', () => { + beforeEach(() => { + // Mock DOM measurements + Object.defineProperty(HTMLElement.prototype, 'offsetWidth', { + configurable: true, + value: 100 + }); + }); + + it('should recalculate visible items based on container width', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + document.body.appendChild(promptTopBar.render); + + // Mock container width + Object.defineProperty(promptTopBar.render, 'offsetWidth', { + value: 800 + }); + + // Mock querySelectorAll to return mock elements + const mockPills = Array.from({ length: 2 }, () => ({ + offsetWidth: 80 + })); + + jest.spyOn(promptTopBar.render, 'querySelectorAll').mockReturnValue(mockPills as any); + + // Spy on the update method + const updateSpy = jest.spyOn(promptTopBar, 'update'); + + (promptTopBar as any).recalculateVisibleItems(); + + // Should not call update if visibleCount doesn't change + expect(updateSpy).not.toHaveBeenCalled(); + }); + + it('should handle width increase scenario', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + document.body.appendChild(promptTopBar.render); + + // Set initial visible count with overflow + promptTopBar.visibleCount = 1; + + // Mock container with large width + Object.defineProperty(promptTopBar.render, 'offsetWidth', { + value: 1200 + }); + + // Mock overflow button + const mockOverflowButton = { offsetWidth: 50 }; + Object.defineProperty(promptTopBar, 'overflowButton', { + value: mockOverflowButton + }); + + // Mock querySelectorAll to return mock elements + const mockPills = Array.from({ length: 1 }, () => ({ + offsetWidth: 80 + })); + + jest.spyOn(promptTopBar.render, 'querySelectorAll').mockReturnValue(mockPills as any); + + // Spy on the update method + const updateSpy = jest.spyOn(promptTopBar, 'update'); + + (promptTopBar as any).recalculateVisibleItems(); + + // Should call update when visibleCount changes + expect(updateSpy).toHaveBeenCalled(); + }); + + it('should handle width decrease scenario', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + document.body.appendChild(promptTopBar.render); + + // Mock container with small width + Object.defineProperty(promptTopBar.render, 'offsetWidth', { + value: 300 + }); + + // Mock querySelectorAll to return mock elements + const mockPills = Array.from({ length: 2 }, () => ({ + offsetWidth: 200 + })); + + jest.spyOn(promptTopBar.render, 'querySelectorAll').mockReturnValue(mockPills as any); + + // Spy on the update method + const updateSpy = jest.spyOn(promptTopBar, 'update'); + + (promptTopBar as any).recalculateVisibleItems(); + + // Should call update when visibleCount changes + expect(updateSpy).toHaveBeenCalled(); + }); + + it('should return early when no context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [] + }); + + const updateSpy = jest.spyOn(promptTopBar, 'update'); + + (promptTopBar as any).recalculateVisibleItems(); + + expect(updateSpy).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-overflow-detailed.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-overflow-detailed.spec.ts new file mode 100644 index 00000000..5cdd3898 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-overflow-detailed.spec.ts @@ -0,0 +1,302 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PromptTopBar } from '../../../../../components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar'; +import { QuickActionCommand } from '../../../../../static'; +import { MynahIcons } from '../../../../../components/icon'; + +// Mock the global events +jest.mock('../../../../../helper/events', () => ({ + MynahUIGlobalEvents: { + getInstance: jest.fn(() => ({ + addListener: jest.fn(), + dispatch: jest.fn() + })) + }, + cancelEvent: jest.fn() +})); + +// Mock the overlay component +const mockUpdateContent = jest.fn(); +const mockClose = jest.fn(); + +jest.mock('../../../../../components/overlay', () => ({ + Overlay: jest.fn().mockImplementation(() => ({ + close: mockClose, + updateContent: mockUpdateContent + })), + OverlayHorizontalDirection: { + START_TO_RIGHT: 'start-to-right', + END_TO_LEFT: 'end-to-left' + }, + OverlayVerticalDirection: { + TO_TOP: 'to-top' + } +})); + +// Mock the detailed list wrapper +const mockDetailedListUpdate = jest.fn(); + +jest.mock('../../../../../components/detailed-list/detailed-list', () => ({ + DetailedListWrapper: jest.fn().mockImplementation(() => ({ + render: document.createElement('div'), + update: mockDetailedListUpdate + })) +})); + +describe('PromptTopBar Overflow Detailed Tests', () => { + let promptTopBar: PromptTopBar; + let mockOnContextItemRemove: jest.Mock; + + const manyContextItems: QuickActionCommand[] = Array.from({ length: 10 }, (_, i) => ({ + id: `context-${i}`, + command: `@file${i}`, + description: `Context item ${i}`, + icon: MynahIcons.FILE + })); + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnContextItemRemove = jest.fn(); + jest.clearAllMocks(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + }); + + describe('Overflow Overlay Generation', () => { + it('should generate overflow overlay children', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems, + onContextItemRemove: mockOnContextItemRemove + }); + + // Set visible count to create overflow + promptTopBar.visibleCount = 5; + + const overlayChildren = promptTopBar.generateOverflowOverlayChildren(); + + expect(overlayChildren).toBeDefined(); + expect(overlayChildren.classList.contains('mynah-chat-prompt-quick-picks-overlay-wrapper')).toBe(true); + }); + + it('should create detailed list wrapper on first call', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems, + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.visibleCount = 5; + + // First call to create the wrapper + promptTopBar.generateOverflowOverlayChildren(); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + expect(DetailedListWrapper).toHaveBeenCalled(); + }); + + it('should update detailed list wrapper on subsequent calls', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems, + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.visibleCount = 5; + + // First call to create the wrapper + promptTopBar.generateOverflowOverlayChildren(); + + // Set up the overflowListContainer property manually for testing + (promptTopBar as any).overflowListContainer = { + update: mockDetailedListUpdate + }; + + // Change visible count to update overflow items + promptTopBar.visibleCount = 3; + + // Call again to trigger update path + promptTopBar.generateOverflowOverlayChildren(); + + expect(mockDetailedListUpdate).toHaveBeenCalled(); + }); + + it('should close overlay when no overflow items remain', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems + }); + + promptTopBar.visibleCount = 5; + + // Create overlay first + promptTopBar.showOverflowOverlay(new Event('click')); + + // Set up the overflowOverlay property manually for testing + (promptTopBar as any).overflowOverlay = { close: mockClose }; + + // Set up the overflowListContainer property manually for testing + (promptTopBar as any).overflowListContainer = { + update: mockDetailedListUpdate + }; + + // Set visible count to show all items (no overflow) + promptTopBar.visibleCount = 10; + + // Generate overlay children again + promptTopBar.generateOverflowOverlayChildren(); + + expect(mockClose).toHaveBeenCalled(); + }); + }); + + describe('Overflow Item Handling', () => { + it('should handle item click in overflow list', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems, + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.visibleCount = 5; + promptTopBar.generateOverflowOverlayChildren(); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + const detailedListCall = DetailedListWrapper.mock.calls[0]; + const detailedListProps = detailedListCall[0]; + + // Simulate item click + const testItem = { id: 'context-5', title: '@file5' }; + detailedListProps.onItemClick(testItem); + + expect(mockOnContextItemRemove).toHaveBeenCalled(); + }); + + it('should handle item action click in overflow list', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems, + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.visibleCount = 5; + promptTopBar.generateOverflowOverlayChildren(); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + const detailedListCall = DetailedListWrapper.mock.calls[0]; + const detailedListProps = detailedListCall[0]; + + // Simulate item action click + const testItem = { id: 'context-5', title: '@file5' }; + detailedListProps.onItemActionClick({}, testItem); + + expect(mockOnContextItemRemove).toHaveBeenCalled(); + }); + + it('should handle item click with title instead of id', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ + { + command: '@noId', + description: 'Item without ID' + } + ], + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.visibleCount = 0; + promptTopBar.generateOverflowOverlayChildren(); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + const detailedListCall = DetailedListWrapper.mock.calls[0]; + const detailedListProps = detailedListCall[0]; + + // Simulate item click with title but no id + const testItem = { title: '@noId' }; + detailedListProps.onItemClick(testItem); + + expect(mockOnContextItemRemove).toHaveBeenCalled(); + }); + + it('should not remove item when item id and title are null', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems, + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.visibleCount = 5; + promptTopBar.generateOverflowOverlayChildren(); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + const detailedListCall = DetailedListWrapper.mock.calls[0]; + const detailedListProps = detailedListCall[0]; + + // Simulate item click with null item + detailedListProps.onItemClick({ id: null, title: null }); + + expect(mockOnContextItemRemove).not.toHaveBeenCalled(); + }); + }); + + describe('Overflow Item Conversion', () => { + it('should convert overflow items to detailed list group format', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: manyContextItems + }); + + promptTopBar.visibleCount = 5; + + const overflowItems = promptTopBar.getOverflowItemsAsDetailedListGroup(); + + expect(overflowItems).toBeDefined(); + expect(overflowItems.length).toBeGreaterThan(0); + + // Check that items have remove actions + const firstGroup = overflowItems[0]; + if ((firstGroup.children != null) && firstGroup.children.length > 0) { + const firstChild = firstGroup.children[0]; + expect(firstChild.actions).toBeDefined(); + expect(firstChild.actions?.[0].id).toBe('remove'); + } + }); + + it('should handle empty overflow items', () => { + // Mock the convertQuickActionCommandGroupsToDetailedListGroups function + jest.mock('../../../../../helper/quick-pick-data-handler', () => ({ + convertQuickActionCommandGroupsToDetailedListGroups: jest.fn().mockReturnValue([]) + })); + + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [] + }); + + // Mock the implementation to return an empty array + jest.spyOn(promptTopBar, 'getOverflowContextItems').mockReturnValue([]); + + const overflowItems = promptTopBar.getOverflowItemsAsDetailedListGroup(); + + // Since we're mocking the return value, we can assert it's an empty array + expect(Array.isArray(overflowItems)).toBe(true); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-overflow.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-overflow.spec.ts new file mode 100644 index 00000000..f415b57e --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar-overflow.spec.ts @@ -0,0 +1,27 @@ +import { PromptTopBar } from '../../../../../components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar'; + +jest.mock('../../../../../helper/events', () => ({ + MynahUIGlobalEvents: { getInstance: () => ({ addListener: jest.fn() }) } +})); + +jest.mock('../../../../../components/overlay', () => ({ + Overlay: jest.fn(() => ({ close: jest.fn() })), + OverlayHorizontalDirection: { END_TO_LEFT: 'end-to-left' }, + OverlayVerticalDirection: { TO_TOP: 'to-top' } +})); + +jest.mock('../../../../../components/detailed-list/detailed-list', () => ({ + DetailedListWrapper: jest.fn(() => ({ render: document.createElement('div') })) +})); + +jest.mock('../../../../../components/chat-item/prompt-input/prompt-top-bar/top-bar-button', () => ({ + TopBarButton: jest.fn(() => ({ render: document.createElement('div') })) +})); + +describe('PromptTopBar Overflow', () => { + it('should show overflow overlay', () => { + const promptTopBar = new PromptTopBar({ tabId: 'test', title: 'Test' }); + promptTopBar.showOverflowOverlay(new Event('click')); + expect(jest.requireMock('../../../../../components/overlay').Overlay).toHaveBeenCalled(); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.spec.ts new file mode 100644 index 00000000..7b0b42cb --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.spec.ts @@ -0,0 +1,880 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PromptTopBar } from '../../../../../components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar'; +import { QuickActionCommand, ChatItemButton, MynahEventNames } from '../../../../../static'; +import { MynahIcons } from '../../../../../components/icon'; +import { MynahUIGlobalEvents } from '../../../../../helper/events'; + +// Mock the cancelEvent function +jest.mock('../../../../../helper/events', () => { + const originalModule = jest.requireActual('../../../../../helper/events'); + return { + ...originalModule, + cancelEvent: jest.fn(), + MynahUIGlobalEvents: { + getInstance: jest.fn(() => ({ + addListener: jest.fn(), + dispatch: jest.fn() + })) + } + }; +}); + +// Mock the overlay component +jest.mock('../../../../../components/overlay', () => ({ + Overlay: jest.fn().mockImplementation(() => ({ + close: jest.fn(), + updateContent: jest.fn() + })), + OverlayHorizontalDirection: { + START_TO_RIGHT: 'start-to-right', + END_TO_LEFT: 'end-to-left' + }, + OverlayVerticalDirection: { + TO_TOP: 'to-top' + } +})); + +// Mock the detailed list wrapper +jest.mock('../../../../../components/detailed-list/detailed-list', () => ({ + DetailedListWrapper: jest.fn().mockImplementation(() => ({ + render: document.createElement('div'), + update: jest.fn() + })) +})); + +// Mock the top bar button +jest.mock('../../../../../components/chat-item/prompt-input/prompt-top-bar/top-bar-button', () => ({ + TopBarButton: jest.fn().mockImplementation(() => ({ + render: document.createElement('div'), + update: jest.fn(), + onTopBarButtonOverlayChanged: jest.fn() + })) +})); + +describe('PromptTopBar Component', () => { + let promptTopBar: PromptTopBar; + let mockOnTopBarTitleClick: jest.Mock; + let mockOnContextItemAdd: jest.Mock; + let mockOnContextItemRemove: jest.Mock; + let mockGlobalEvents: any; + + const basicContextItems: QuickActionCommand[] = [ + { + id: 'context-1', + command: '@file1', + description: 'First context item', + icon: MynahIcons.FILE + }, + { + id: 'context-2', + command: '@file2', + description: 'Second context item', + icon: MynahIcons.FILE + } + ]; + + const basicTopBarButton: ChatItemButton = { + id: 'top-bar-button', + text: 'Action', + icon: MynahIcons.PLUS + }; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnTopBarTitleClick = jest.fn(); + mockOnContextItemAdd = jest.fn(); + mockOnContextItemRemove = jest.fn(); + + mockGlobalEvents = { + addListener: jest.fn(), + dispatch: jest.fn() + }; + + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue(mockGlobalEvents); + + jest.clearAllMocks(); + jest.clearAllTimers(); + jest.useFakeTimers(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + describe('Basic Functionality', () => { + it('should create prompt top bar with basic props', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + expect(promptTopBar.render).toBeDefined(); + expect(promptTopBar.render.classList.contains('mynah-prompt-input-top-bar')).toBe(true); + }); + + it('should have correct test ID', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + document.body.appendChild(promptTopBar.render); + + const topBar = document.body.querySelector('[data-testid*="prompt-top-bar"]'); + expect(topBar).toBeDefined(); + }); + + it('should register for global events', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + expect(mockGlobalEvents.addListener).toHaveBeenCalledWith( + MynahEventNames.CONTEXT_PINNED, + expect.any(Function) + ); + expect(mockGlobalEvents.addListener).toHaveBeenCalledWith( + MynahEventNames.ROOT_RESIZE, + expect.any(Function) + ); + }); + + it('should set up resize observer', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + expect(mockGlobalEvents.addListener).toHaveBeenCalledWith( + MynahEventNames.ROOT_RESIZE, + expect.any(Function) + ); + }); + + it('should calculate visible count from context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + expect(promptTopBar.visibleCount).toBe(2); + }); + + it('should handle empty context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [] + }); + + expect(promptTopBar.visibleCount).toBe(0); + }); + + it('should handle undefined context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + expect(promptTopBar.visibleCount).toBe(0); + }); + }); + + describe('Hidden State', () => { + it('should be hidden when title is null', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab' + }); + + expect(promptTopBar.isHidden()).toBe(true); + expect(promptTopBar.render.classList.contains('hidden')).toBe(true); + }); + + it('should be hidden when title is empty string', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: '' + }); + + expect(promptTopBar.isHidden()).toBe(true); + expect(promptTopBar.render.classList.contains('hidden')).toBe(true); + }); + + it('should not be hidden when title is provided', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + expect(promptTopBar.isHidden()).toBe(false); + expect(promptTopBar.render.classList.contains('hidden')).toBe(false); + }); + }); + + describe('Title Generation', () => { + it('should generate title button when title is provided', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + onTopBarTitleClick: mockOnTopBarTitleClick + }); + document.body.appendChild(promptTopBar.render); + + const titleButton = document.body.querySelector('button'); + expect(titleButton).toBeDefined(); + expect(titleButton?.textContent).toContain('Test Title'); + }); + + it('should handle title click', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + onTopBarTitleClick: mockOnTopBarTitleClick + }); + document.body.appendChild(promptTopBar.render); + + // Instead of clicking the button directly, call the onTopBarTitleClick callback + (promptTopBar as any).props.onTopBarTitleClick(); + + expect(mockOnTopBarTitleClick).toHaveBeenCalled(); + }); + + it('should return empty string when title is null', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab' + }); + + const titleElement = promptTopBar.generateTitle(); + expect(titleElement).toBe(''); + }); + + it('should update existing title button', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Original Title' + }); + + // Generate title first time + promptTopBar.generateTitle(); + + // Update title + promptTopBar.update({ title: 'Updated Title' }); + + expect(promptTopBar.render).toBeDefined(); + }); + }); + + describe('Context Pills', () => { + it('should generate context pills for context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + document.body.appendChild(promptTopBar.render); + + const contextPills = document.body.querySelectorAll('.pinned-context-pill:not(.overflow-button)'); + expect(contextPills.length).toBe(2); + }); + + it('should render context pill with correct content', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill'); + expect(contextPill?.textContent).toContain('file1'); + }); + + it('should handle context pill click to remove', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ ...basicContextItems ], + onContextItemRemove: mockOnContextItemRemove + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill') as HTMLElement; + contextPill.click(); + + expect(mockOnContextItemRemove).toHaveBeenCalledWith(basicContextItems[0]); + }); + + it('should show context tooltip on hover', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill') as HTMLElement; + + // Trigger mouseenter + const mouseEnterEvent = new MouseEvent('mouseenter'); + contextPill.dispatchEvent(mouseEnterEvent); + + // Fast-forward timer + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should hide context tooltip on mouseleave', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill') as HTMLElement; + + // Trigger mouseenter then mouseleave + contextPill.dispatchEvent(new MouseEvent('mouseenter')); + contextPill.dispatchEvent(new MouseEvent('mouseleave')); + + // Fast-forward timer + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../../../components/overlay'); + expect(Overlay).not.toHaveBeenCalled(); + }); + + it('should return empty array when no context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + const contextPills = promptTopBar.generateContextPills(); + expect(contextPills).toEqual([]); + }); + + it('should handle context items without icon', () => { + const contextItemWithoutIcon: QuickActionCommand = { + id: 'no-icon', + command: '@noicon', + description: 'No icon item' + }; + + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ contextItemWithoutIcon ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill'); + expect(contextPill).toBeDefined(); + }); + + it('should strip @ symbol from command in label', () => { + const contextItemWithAt: QuickActionCommand = { + id: 'with-at', + command: '@filename', + description: 'File with @ symbol' + }; + + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ contextItemWithAt ] + }); + document.body.appendChild(promptTopBar.render); + + const contextPill = document.body.querySelector('.pinned-context-pill .label'); + expect(contextPill?.textContent).toBe('filename'); + }); + }); + + describe('Visible and Overflow Items', () => { + it('should get visible context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + promptTopBar.visibleCount = 1; + const visibleItems = promptTopBar.getVisibleContextItems(); + + expect(visibleItems.length).toBe(1); + expect(visibleItems[0]).toBe(basicContextItems[0]); + }); + + it('should get overflow context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + promptTopBar.visibleCount = 1; + const overflowItems = promptTopBar.getOverflowContextItems(); + + expect(overflowItems.length).toBe(1); + expect(overflowItems[0]).toBe(basicContextItems[1]); + }); + + it('should return empty array for visible items when no context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + const visibleItems = promptTopBar.getVisibleContextItems(); + expect(visibleItems).toEqual([]); + }); + + it('should return empty array for overflow items when no context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + const overflowItems = promptTopBar.getOverflowContextItems(); + expect(overflowItems).toEqual([]); + }); + + it('should calculate overflow count correctly', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + promptTopBar.visibleCount = 1; + const overflowCount = promptTopBar.getOverflowCount(); + + expect(overflowCount).toBe(1); + }); + + it('should return 0 overflow count when no context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + const overflowCount = promptTopBar.getOverflowCount(); + expect(overflowCount).toBe(0); + }); + }); + + describe('Overflow Pill', () => { + it('should generate overflow pill when there are overflow items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + promptTopBar.visibleCount = 1; + const overflowPill = promptTopBar.generateOverflowPill(); + + expect(overflowPill).not.toBe(''); + }); + + it('should return empty string when no overflow items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + promptTopBar.visibleCount = 2; + const overflowPill = promptTopBar.generateOverflowPill(); + + expect(overflowPill).toBe(''); + }); + + it('should show correct overflow count in pill', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ ...basicContextItems, ...basicContextItems ] // 4 items + }); + document.body.appendChild(promptTopBar.render); + + promptTopBar.visibleCount = 2; + promptTopBar.update(); + + const overflowPill = document.body.querySelector('.overflow-button'); + expect(overflowPill?.textContent).toBe('+2'); + }); + + it('should handle overflow pill click', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ ...basicContextItems, ...basicContextItems ] + }); + document.body.appendChild(promptTopBar.render); + + promptTopBar.visibleCount = 2; + promptTopBar.update(); + + const overflowPill = document.body.querySelector('.overflow-button') as HTMLElement; + overflowPill.click(); + + const { Overlay } = jest.requireMock('../../../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should update existing overflow pill', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ ...basicContextItems, ...basicContextItems ] + }); + + promptTopBar.visibleCount = 2; + + // Generate overflow pill first time + promptTopBar.generateOverflowPill(); + + // Generate again (should update existing) + promptTopBar.visibleCount = 1; + const overflowPill = promptTopBar.generateOverflowPill(); + + expect(overflowPill).not.toBe(''); + }); + }); + + describe('Context Item Management', () => { + it('should add context pill', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ], + onContextItemAdd: mockOnContextItemAdd + }); + + const newContextItem: QuickActionCommand = { + id: 'new-context', + command: '@newfile', + description: 'New context item' + }; + + promptTopBar.addContextPill(newContextItem); + + expect(mockOnContextItemAdd).toHaveBeenCalledWith(newContextItem); + }); + + it('should not add duplicate context pill', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ ...basicContextItems ], + onContextItemAdd: mockOnContextItemAdd + }); + + // Try to add existing item + promptTopBar.addContextPill(basicContextItems[0]); + + expect(mockOnContextItemAdd).not.toHaveBeenCalled(); + }); + + it('should remove context pill by id', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ ...basicContextItems ], + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.removeContextPill('context-1'); + + expect(mockOnContextItemRemove).toHaveBeenCalledWith(basicContextItems[0]); + }); + + it('should remove context pill by command when id is not available', () => { + const contextItemWithoutId: QuickActionCommand = { + command: '@noId', + description: 'No ID item' + }; + + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ contextItemWithoutId ], + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.removeContextPill('@noId'); + + expect(mockOnContextItemRemove).toHaveBeenCalledWith(contextItemWithoutId); + }); + + it('should not remove non-existent context pill', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ ...basicContextItems ], + onContextItemRemove: mockOnContextItemRemove + }); + + promptTopBar.removeContextPill('non-existent'); + + expect(mockOnContextItemRemove).not.toHaveBeenCalled(); + }); + + it('should handle context pinned global event', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [], + onContextItemAdd: mockOnContextItemAdd + }); + + // Get the context pinned listener + const contextPinnedCall = mockGlobalEvents.addListener.mock.calls.find( + (call: any) => call[0] === MynahEventNames.CONTEXT_PINNED + ); + const contextPinnedListener = contextPinnedCall[1]; + + const newContextItem: QuickActionCommand = { + id: 'pinned-context', + command: '@pinned', + description: 'Pinned context item' + }; + + // Simulate context pinned event + contextPinnedListener({ + tabId: 'test-tab', + contextItem: newContextItem + }); + + expect(mockOnContextItemAdd).toHaveBeenCalledWith(newContextItem); + }); + + it('should ignore context pinned event for different tab', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [], + onContextItemAdd: mockOnContextItemAdd + }); + + const contextPinnedCall = mockGlobalEvents.addListener.mock.calls.find( + (call: any) => call[0] === MynahEventNames.CONTEXT_PINNED + ); + const contextPinnedListener = contextPinnedCall[1]; + + const newContextItem: QuickActionCommand = { + id: 'pinned-context', + command: '@pinned', + description: 'Pinned context item' + }; + + // Simulate context pinned event for different tab + contextPinnedListener({ + tabId: 'different-tab', + contextItem: newContextItem + }); + + expect(mockOnContextItemAdd).not.toHaveBeenCalled(); + }); + }); + + describe('Update Functionality', () => { + it('should update context items', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + + // Mock the recalculateVisibleItems method to do nothing + const recalculateSpy = jest.spyOn(promptTopBar as any, 'recalculateVisibleItems').mockImplementation(() => {}); + + promptTopBar.update({ + contextItems: basicContextItems + }); + + // Verify that recalculateVisibleItems was called + expect(recalculateSpy).toHaveBeenCalled(); + }); + + it('should update title', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Original Title' + }); + + promptTopBar.update({ + title: 'Updated Title' + }); + + expect(promptTopBar.render).toBeDefined(); + }); + + it('should update top bar button', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + topBarButton: basicTopBarButton + }); + + const updatedButton: ChatItemButton = { + id: 'updated-button', + text: 'Updated Action', + icon: MynahIcons.REFRESH + }; + + promptTopBar.update({ + topBarButton: updatedButton + }); + + expect(promptTopBar.topBarButton.update).toHaveBeenCalledWith({ + topBarButton: updatedButton + }); + }); + + it('should toggle hidden class based on title', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + expect(promptTopBar.render.classList.contains('hidden')).toBe(false); + + promptTopBar.update({ title: '' }); + + expect(promptTopBar.render.classList.contains('hidden')).toBe(true); + }); + + it('should recalculate visible items when context items change', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: [ basicContextItems[0] ] + }); + + const recalculateSpy = jest.spyOn(promptTopBar as any, 'recalculateVisibleItems'); + + promptTopBar.update({ + contextItems: basicContextItems + }); + + expect(recalculateSpy).toHaveBeenCalled(); + }); + + it('should recalculate visible items when title changes', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Original Title', + contextItems: basicContextItems + }); + + const recalculateSpy = jest.spyOn(promptTopBar as any, 'recalculateVisibleItems'); + + promptTopBar.update({ + title: 'Updated Title' + }); + + expect(recalculateSpy).toHaveBeenCalled(); + }); + }); + + describe('Top Bar Button Integration', () => { + it('should update top bar button overlay', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + topBarButton: basicTopBarButton + }); + + const overlayData = { + list: [ + { + groupName: 'Test Group', + children: [ + { id: 'item-1', title: 'Test Item' } + ] + } + ] + }; + + promptTopBar.updateTopBarButtonOverlay(overlayData); + + expect(promptTopBar.topBarButton.onTopBarButtonOverlayChanged).toHaveBeenCalledWith(overlayData); + }); + }); + + describe('Resize Handling', () => { + it('should handle resize events', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + const resizeCall = mockGlobalEvents.addListener.mock.calls.find( + (call: any) => call[0] === MynahEventNames.ROOT_RESIZE + ); + const resizeListener = resizeCall[1]; + + const recalculateSpy = jest.spyOn(promptTopBar as any, 'recalculateVisibleItems'); + + // Simulate resize event + resizeListener(); + + expect(recalculateSpy).toHaveBeenCalled(); + }); + }); + + describe('Timeout Handling', () => { + it('should trigger recalculation after timeout', () => { + const recalculateSpy = jest.fn(); + + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + contextItems: basicContextItems + }); + + // Mock the recalculateVisibleItems method + (promptTopBar as any).recalculateVisibleItems = recalculateSpy; + + // Fast-forward the timeout + jest.advanceTimersByTime(100); + + expect(recalculateSpy).toHaveBeenCalled(); + }); + }); + + describe('Custom Class Names', () => { + it('should apply custom class names', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title', + classNames: [ 'custom-class-1', 'custom-class-2' ] + }); + + expect(promptTopBar.render.classList.contains('custom-class-1')).toBe(true); + expect(promptTopBar.render.classList.contains('custom-class-2')).toBe(true); + }); + + it('should handle undefined class names', () => { + promptTopBar = new PromptTopBar({ + tabId: 'test-tab', + title: 'Test Title' + }); + + expect(promptTopBar.render.classList.contains('mynah-prompt-input-top-bar')).toBe(true); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/top-bar-button-overlay.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/top-bar-button-overlay.spec.ts new file mode 100644 index 00000000..03764d2f --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/top-bar-button-overlay.spec.ts @@ -0,0 +1,284 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TopBarButton, TopBarButtonOverlayProps } from '../../../../../components/chat-item/prompt-input/prompt-top-bar/top-bar-button'; +import { ChatItemButton, DetailedList } from '../../../../../static'; +import { MynahIcons } from '../../../../../components/icon'; + +// Mock the overlay component +const mockUpdateContent = jest.fn(); +const mockClose = jest.fn(); + +jest.mock('../../../../../components/overlay', () => ({ + Overlay: jest.fn().mockImplementation(() => ({ + close: mockClose, + updateContent: mockUpdateContent + })), + OverlayHorizontalDirection: { + END_TO_LEFT: 'end-to-left' + }, + OverlayVerticalDirection: { + TO_TOP: 'to-top' + } +})); + +// Mock the detailed list wrapper +const mockDetailedListUpdate = jest.fn(); + +jest.mock('../../../../../components/detailed-list/detailed-list', () => ({ + DetailedListWrapper: jest.fn().mockImplementation(() => ({ + render: document.createElement('div'), + update: mockDetailedListUpdate + })) +})); + +describe('TopBarButton Overlay Functionality', () => { + let topBarButton: TopBarButton; + let mockOnKeyPress: jest.Mock; + let mockOnGroupClick: jest.Mock; + let mockOnItemClick: jest.Mock; + let mockOnClose: jest.Mock; + + const basicButton: ChatItemButton = { + id: 'test-button', + text: 'Test Button', + icon: MynahIcons.PLUS + }; + + const basicOverlayData: TopBarButtonOverlayProps = { + tabId: 'test-tab', + topBarButtonOverlay: { + list: [ + { + groupName: 'Test Group', + children: [ + { + id: 'item-1', + title: 'Test Item 1', + description: 'Description 1' + } + ] + } + ] + } + }; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnKeyPress = jest.fn(); + mockOnGroupClick = jest.fn(); + mockOnItemClick = jest.fn(); + mockOnClose = jest.fn(); + jest.clearAllMocks(); + + // Mock window event listeners + global.addEventListener = jest.fn(); + global.removeEventListener = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + }); + + describe('Overlay Creation and Updates', () => { + it('should create overlay with event handlers', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + const overlayData: TopBarButtonOverlayProps = { + ...basicOverlayData, + events: { + onKeyPress: mockOnKeyPress, + onGroupClick: mockOnGroupClick, + onItemClick: mockOnItemClick, + onClose: mockOnClose + } + }; + + topBarButton.showOverlay(overlayData); + + const { Overlay } = jest.requireMock('../../../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + expect(global.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); + + it('should update existing overlay content', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + // Show overlay first time + topBarButton.showOverlay(basicOverlayData); + + // Show overlay second time (should update existing) + const updatedOverlayData: TopBarButtonOverlayProps = { + ...basicOverlayData, + topBarButtonOverlay: { + list: [ + { + groupName: 'Updated Group', + children: [ + { id: 'updated-item', title: 'Updated Item' } + ] + } + ] + } + }; + + topBarButton.showOverlay(updatedOverlayData); + + expect(mockUpdateContent).toHaveBeenCalled(); + }); + + it('should handle overlay close callback', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + const overlayData: TopBarButtonOverlayProps = { + ...basicOverlayData, + events: { + onClose: mockOnClose + } + }; + + topBarButton.showOverlay(overlayData); + + const overlayCall = jest.requireMock('../../../../../components/overlay').Overlay.mock.calls[0]; + const overlayProps = overlayCall[0]; + + // Simulate overlay close + overlayProps.onClose(); + + expect(mockOnClose).toHaveBeenCalled(); + expect(global.removeEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); + }); + + describe('Detailed List Wrapper', () => { + it('should create detailed list wrapper on first call', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + const overlayData: TopBarButtonOverlayProps = { + ...basicOverlayData, + events: { + onGroupClick: mockOnGroupClick, + onItemClick: mockOnItemClick + } + }; + + topBarButton.showOverlay(overlayData); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + expect(DetailedListWrapper).toHaveBeenCalled(); + }); + + it('should update detailed list wrapper on subsequent calls', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + // First call to create the wrapper + topBarButton.showOverlay(basicOverlayData); + + // Set up the overlayData property manually for testing + (topBarButton as any).overlayData = basicOverlayData; + (topBarButton as any).checklistSelectorContainer = { + update: mockDetailedListUpdate + }; + + // Call getItemGroups directly to trigger the update path + (topBarButton as any).getItemGroups(); + + expect(mockDetailedListUpdate).toHaveBeenCalled(); + }); + + it('should handle group action click with null group name', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + const overlayData: TopBarButtonOverlayProps = { + ...basicOverlayData, + events: { + onGroupClick: mockOnGroupClick + } + }; + + topBarButton.showOverlay(overlayData); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + const detailedListCall = DetailedListWrapper.mock.calls[0]; + const detailedListProps = detailedListCall[0]; + + // Simulate group action click with null group name + detailedListProps.onGroupActionClick({}, null); + + expect(mockOnGroupClick).not.toHaveBeenCalled(); + }); + + it('should handle item action click with null item', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + const overlayData: TopBarButtonOverlayProps = { + ...basicOverlayData, + events: { + onItemClick: mockOnItemClick + } + }; + + topBarButton.showOverlay(overlayData); + + const { DetailedListWrapper } = jest.requireMock('../../../../../components/detailed-list/detailed-list'); + const detailedListCall = DetailedListWrapper.mock.calls[0]; + const detailedListProps = detailedListCall[0]; + + // Simulate item action click with null item + detailedListProps.onItemActionClick({}, null); + + expect(mockOnItemClick).not.toHaveBeenCalled(); + }); + }); + + describe('Overlay Content Updates', () => { + it('should update overlay content when topBarButtonOverlay changes', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + // Show overlay first + topBarButton.showOverlay(basicOverlayData); + + // Set up the overlay property manually for testing + (topBarButton as any).overlay = { updateContent: mockUpdateContent }; + + const updatedOverlay: DetailedList = { + list: [ + { + groupName: 'New Group', + children: [ + { id: 'new-item', title: 'New Item' } + ] + } + ] + }; + + topBarButton.onTopBarButtonOverlayChanged(updatedOverlay); + + expect(mockUpdateContent).toHaveBeenCalled(); + }); + + it('should not update overlay content when overlay is not shown', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + // Set up the overlayData property manually for testing + (topBarButton as any).overlayData = basicOverlayData; + + const updatedOverlay: DetailedList = { + list: [ + { + groupName: 'New Group', + children: [ + { id: 'new-item', title: 'New Item' } + ] + } + ] + }; + + // Should not throw error when overlay is not shown + expect(() => topBarButton.onTopBarButtonOverlayChanged(updatedOverlay)).not.toThrow(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/top-bar-button.spec.ts b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/top-bar-button.spec.ts new file mode 100644 index 00000000..d66b7324 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/chat-item/prompt-input/prompt-top-bar/top-bar-button.spec.ts @@ -0,0 +1,136 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TopBarButton } from '../../../../../components/chat-item/prompt-input/prompt-top-bar/top-bar-button'; +import { ChatItemButton } from '../../../../../static'; +import { MynahIcons } from '../../../../../components/icon'; + +// Mock the overlay component +jest.mock('../../../../../components/overlay', () => ({ + Overlay: jest.fn().mockImplementation(() => ({ + close: jest.fn(), + updateContent: jest.fn() + })), + OverlayHorizontalDirection: { + END_TO_LEFT: 'end-to-left' + }, + OverlayVerticalDirection: { + TO_TOP: 'to-top' + } +})); + +// Mock the detailed list wrapper +jest.mock('../../../../../components/detailed-list/detailed-list', () => ({ + DetailedListWrapper: jest.fn().mockImplementation(() => ({ + render: document.createElement('div'), + update: jest.fn() + })) +})); + +describe('TopBarButton Component', () => { + let topBarButton: TopBarButton; + let mockOnTopBarButtonClick: jest.Mock; + + const basicButton: ChatItemButton = { + id: 'test-button', + text: 'Test Button', + icon: MynahIcons.PLUS + }; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnTopBarButtonClick = jest.fn(); + jest.clearAllMocks(); + + // Mock window event listeners + global.addEventListener = jest.fn(); + global.removeEventListener = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + }); + + describe('Basic Functionality', () => { + it('should create top bar button with basic props', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + expect(topBarButton.render).toBeDefined(); + expect(topBarButton.render.classList.contains('top-bar-button')).toBe(true); + }); + + it('should create top bar button without button prop', () => { + topBarButton = new TopBarButton({}); + + expect(topBarButton.render).toBeDefined(); + }); + + it('should have correct test ID', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + document.body.appendChild(topBarButton.render); + + const button = document.body.querySelector('[data-testid*="top-bar-button"]'); + expect(button).toBeDefined(); + }); + + it('should have contenteditable false attribute', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + expect(topBarButton.render.getAttribute('contenteditable')).toBe('false'); + }); + }); + + describe('Button Click Handling', () => { + it('should handle button click', () => { + topBarButton = new TopBarButton({ + topBarButton: basicButton, + onTopBarButtonClick: mockOnTopBarButtonClick + }); + document.body.appendChild(topBarButton.render); + + const buttonElement = document.body.querySelector('button') as HTMLElement; + buttonElement.click(); + + expect(mockOnTopBarButtonClick).toHaveBeenCalledWith(basicButton); + }); + + it('should not call callback when topBarButton is null', () => { + topBarButton = new TopBarButton({ + onTopBarButtonClick: mockOnTopBarButtonClick + }); + document.body.appendChild(topBarButton.render); + + const buttonElement = document.body.querySelector('button') as HTMLElement; + buttonElement.click(); + + expect(mockOnTopBarButtonClick).not.toHaveBeenCalled(); + }); + }); + + describe('Update Functionality', () => { + it('should update button properties', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + const updatedButton: ChatItemButton = { + id: 'updated-button', + text: 'Updated Button', + icon: MynahIcons.REFRESH + }; + + topBarButton.update({ topBarButton: updatedButton }); + + expect(topBarButton.render).toBeDefined(); + }); + + it('should handle update with null button', () => { + topBarButton = new TopBarButton({ topBarButton: basicButton }); + + topBarButton.update({}); + + expect(topBarButton.render).toBeDefined(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list-item.spec.ts b/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list-item.spec.ts new file mode 100644 index 00000000..6fe1d538 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list-item.spec.ts @@ -0,0 +1,1019 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DetailedListItemWrapper } from '../../../components/detailed-list/detailed-list-item'; +import { DetailedListItem } from '../../../static'; +import { MynahIcons } from '../../../components/icon'; + +// Mock the overlay component +jest.mock('../../../components/overlay', () => ({ + Overlay: jest.fn().mockImplementation(() => ({ + close: jest.fn() + })), + OverlayHorizontalDirection: { + CENTER: 'center', + END_TO_LEFT: 'end-to-left' + }, + OverlayVerticalDirection: { + TO_TOP: 'to-top', + CENTER: 'center' + } +})); + +describe('DetailedListItemWrapper Component', () => { + let detailedListItem: DetailedListItemWrapper; + let mockOnSelect: jest.Mock; + let mockOnClick: jest.Mock; + let mockOnActionClick: jest.Mock; + + const basicListItem: DetailedListItem = { + id: 'item-1', + title: 'Test Item', + description: 'This is a test item description' + }; + + const itemWithIcon: DetailedListItem = { + id: 'item-2', + title: 'Item with Icon', + description: 'Item with icon description', + icon: MynahIcons.OK, + iconForegroundStatus: 'success' + }; + + const itemWithStatus: DetailedListItem = { + id: 'item-3', + title: 'Item with Status', + description: 'Item with status description', + status: { + status: 'warning', + text: 'Warning', + icon: MynahIcons.WARNING, + description: 'This is a warning status' + } + }; + + const itemWithActions: DetailedListItem = { + id: 'item-4', + title: 'Item with Actions', + description: 'Item with actions description', + actions: [ + { + id: 'action-1', + text: 'Edit', + icon: MynahIcons.PENCIL, + description: 'Edit this item' + }, + { + id: 'action-2', + text: 'Delete', + icon: MynahIcons.CANCEL, + description: 'Delete this item', + status: 'error' + } + ] + }; + + const itemWithChildren: DetailedListItem = { + id: 'item-5', + title: 'Item with Children', + description: 'Item with children description', + children: [ + { + groupName: 'Child Group', + children: [ + { id: 'child-1', title: 'Child Item 1' } + ] + } + ] + }; + + const disabledItem: DetailedListItem = { + id: 'item-6', + title: 'Disabled Item', + description: 'This item is disabled', + disabled: true + }; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnSelect = jest.fn(); + mockOnClick = jest.fn(); + mockOnActionClick = jest.fn(); + jest.clearAllMocks(); + jest.clearAllTimers(); + jest.useFakeTimers(); + + // Mock scrollIntoView + Element.prototype.scrollIntoView = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + describe('Basic Functionality', () => { + it('should create detailed list item with basic props', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + + expect(detailedListItem.render).toBeDefined(); + expect(detailedListItem.render.classList.contains('mynah-detailed-list-item')).toBe(true); + }); + + it('should render item title', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const title = document.body.querySelector('.mynah-detailed-list-item-name'); + expect(title?.textContent).toBe(basicListItem.title); + }); + + it('should render item description', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const description = document.body.querySelector('.mynah-detailed-list-item-description'); + // The description is processed by markdown parser and wrapped in bdi with   + expect(description?.innerHTML).toContain('This is a test item description'); + }); + + it('should use name when title is not provided', () => { + const itemWithName: DetailedListItem = { + id: 'item-name', + name: 'Item Name', + description: 'Description' + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithName }); + document.body.appendChild(detailedListItem.render); + + const title = document.body.querySelector('.mynah-detailed-list-item-name'); + expect(title?.textContent).toBe(itemWithName.name); + }); + + it('should have correct test ID', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const item = document.body.querySelector('[data-testid*="quick-pick-item"]'); + expect(item).toBeDefined(); + }); + }); + + describe('Icon Rendering', () => { + it('should render icon when provided', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithIcon }); + document.body.appendChild(detailedListItem.render); + + const iconContainer = document.body.querySelector('.mynah-detailed-list-icon'); + expect(iconContainer).toBeDefined(); + + const icon = iconContainer?.querySelector('.mynah-icon'); + expect(icon).toBeDefined(); + }); + + it('should not render icon container when icon is not provided', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const iconContainer = document.body.querySelector('.mynah-detailed-list-icon'); + expect(iconContainer).toBeNull(); + }); + + it('should apply icon foreground status', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithIcon }); + document.body.appendChild(detailedListItem.render); + + const icon = document.body.querySelector('.mynah-icon'); + expect(icon).toBeDefined(); + }); + }); + + describe('Status Rendering', () => { + it('should render status when provided', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatus }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status'); + expect(status).toBeDefined(); + expect(status?.classList.contains('status-warning')).toBe(true); + }); + + it('should render status text', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatus }); + document.body.appendChild(detailedListItem.render); + + const statusText = document.body.querySelector('.mynah-detailed-list-item-status span'); + expect(statusText?.textContent).toBe(itemWithStatus.status?.text); + }); + + it('should render status icon', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatus }); + document.body.appendChild(detailedListItem.render); + + const statusIcon = document.body.querySelector('.mynah-detailed-list-item-status .mynah-icon'); + expect(statusIcon).toBeDefined(); + }); + + it('should show tooltip on status hover when description is provided', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatus }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status') as HTMLElement; + + // Trigger mouseover + const mouseOverEvent = new MouseEvent('mouseover'); + Object.defineProperty(mouseOverEvent, 'currentTarget', { + value: status, + enumerable: true + }); + status.dispatchEvent(mouseOverEvent); + + // Fast-forward timer + jest.advanceTimersByTime(350); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should hide tooltip on mouseleave', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatus }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status') as HTMLElement; + + // Trigger mouseover then mouseleave + status.dispatchEvent(new MouseEvent('mouseover')); + status.dispatchEvent(new MouseEvent('mouseleave')); + + // Fast-forward timer + jest.advanceTimersByTime(350); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).not.toHaveBeenCalled(); + }); + + it('should handle status without description', () => { + const itemWithStatusNoDesc: DetailedListItem = { + id: 'item-status-no-desc', + title: 'Item', + status: { + status: 'info', + text: 'Info', + icon: MynahIcons.INFO + } + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatusNoDesc }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status'); + expect(status).toBeDefined(); + }); + }); + + describe('Actions Rendering', () => { + it('should render single action as button', () => { + const itemWithSingleAction: DetailedListItem = { + id: 'single-action', + title: 'Single Action Item', + actions: (itemWithActions.actions != null) ? [ itemWithActions.actions[0] ] : [] + }; + + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithSingleAction, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + const actionsContainer = document.body.querySelector('.mynah-detailed-list-item-actions'); + expect(actionsContainer).toBeDefined(); + + const actionButton = actionsContainer?.querySelector('button'); + expect(actionButton).toBeDefined(); + }); + + it('should render multiple actions as menu when groupActions is not false', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + const actionsContainer = document.body.querySelector('.mynah-detailed-list-item-actions'); + expect(actionsContainer).toBeDefined(); + + const menuButton = actionsContainer?.querySelector('[data-testid*="action-menu"]'); + expect(menuButton).toBeDefined(); + }); + + it('should render multiple actions as individual buttons when groupActions is false', () => { + const itemWithUngroupedActions: DetailedListItem = { + ...itemWithActions, + groupActions: false + }; + + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithUngroupedActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + const actionsContainer = document.body.querySelector('.mynah-detailed-list-item-actions'); + expect(actionsContainer).toBeDefined(); + + const actionButtons = actionsContainer?.querySelectorAll('button'); + expect(actionButtons?.length).toBe(2); + }); + + it('should handle action click', () => { + const itemWithSingleAction: DetailedListItem = { + id: 'single-action', + title: 'Single Action Item', + actions: (itemWithActions.actions != null) ? [ itemWithActions.actions[0] ] : [] + }; + + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithSingleAction, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + const actionButton = document.body.querySelector('.mynah-detailed-list-item-actions button') as HTMLElement; + actionButton.click(); + + expect(mockOnActionClick).toHaveBeenCalledWith(itemWithActions.actions?.[0], itemWithSingleAction); + }); + + it('should show action menu overlay when menu button is clicked', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + const menuButton = document.body.querySelector('[data-testid*="action-menu"]') as HTMLElement; + if (menuButton != null) { + menuButton.click(); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + } else { + // If no menu button found, it means actions are rendered individually + const actionButtons = document.body.querySelectorAll('.mynah-detailed-list-item-actions button'); + expect(actionButtons.length).toBeGreaterThan(0); + } + }); + }); + + describe('Children Indicator', () => { + it('should render arrow icon when item has children', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithChildren }); + document.body.appendChild(detailedListItem.render); + + const arrowIcon = document.body.querySelector('.mynah-detailed-list-item-arrow-icon'); + expect(arrowIcon).toBeDefined(); + + const icon = arrowIcon?.querySelector('.mynah-icon'); + expect(icon).toBeDefined(); + }); + + it('should not render arrow icon when item has no children', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const arrowIcon = document.body.querySelector('.mynah-detailed-list-item-arrow-icon'); + expect(arrowIcon).toBeNull(); + }); + + it('should not render arrow icon when children array is empty', () => { + const itemWithEmptyChildren: DetailedListItem = { + id: 'empty-children', + title: 'Empty Children', + children: [] + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithEmptyChildren }); + document.body.appendChild(detailedListItem.render); + + const arrowIcon = document.body.querySelector('.mynah-detailed-list-item-arrow-icon'); + expect(arrowIcon).toBeNull(); + }); + }); + + describe('Disabled Text', () => { + it('should render disabled text when disabledText is present without children', () => { + const itemWithDisabledTextOnly: DetailedListItem = { + id: 'disabled-text-only', + title: 'Item with Disabled Text Only', + disabledText: 'pending' + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithDisabledTextOnly }); + document.body.appendChild(detailedListItem.render); + + const disabledText = document.body.querySelector('.mynah-detailed-list-item-disabled-text'); + expect(disabledText).toBeDefined(); + expect(disabledText?.textContent).toBe('(pending)'); + + const arrowIcon = document.body.querySelector('.mynah-detailed-list-item-arrow-icon'); + expect(arrowIcon).toBeNull(); + }); + + it('should render disabled text instead of arrow when disabledText is present with children', () => { + const itemWithDisabledTextAndChildren: DetailedListItem = { + id: 'disabled-text-with-children', + title: 'Item with Disabled Text and Children', + children: [ + { + groupName: 'Child Group', + children: [ + { id: 'child-1', title: 'Child Item 1' } + ] + } + ], + disabledText: 'pending' + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithDisabledTextAndChildren }); + document.body.appendChild(detailedListItem.render); + + const disabledText = document.body.querySelector('.mynah-detailed-list-item-disabled-text'); + expect(disabledText).toBeDefined(); + expect(disabledText?.textContent).toBe('(pending)'); + + const arrowIcon = document.body.querySelector('.mynah-detailed-list-item-arrow-icon'); + expect(arrowIcon).toBeNull(); + }); + }); + + describe('Click Handling', () => { + it('should handle select click when selectable', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: basicListItem, + onSelect: mockOnSelect, + selectable: true + }); + document.body.appendChild(detailedListItem.render); + + detailedListItem.render.click(); + + expect(mockOnSelect).toHaveBeenCalledWith(basicListItem); + }); + + it('should handle click when clickable', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: basicListItem, + onClick: mockOnClick, + clickable: true + }); + document.body.appendChild(detailedListItem.render); + + detailedListItem.render.click(); + + expect(mockOnClick).toHaveBeenCalledWith(basicListItem); + }); + + it('should not handle clicks when item is disabled', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: disabledItem, + onSelect: mockOnSelect, + onClick: mockOnClick, + selectable: true, + clickable: true + }); + document.body.appendChild(detailedListItem.render); + + detailedListItem.render.click(); + + expect(mockOnSelect).not.toHaveBeenCalled(); + expect(mockOnClick).not.toHaveBeenCalled(); + }); + + it('should prevent mousedown event', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const mouseDownEvent = new MouseEvent('mousedown', { bubbles: true }); + const preventDefaultSpy = jest.spyOn(mouseDownEvent, 'preventDefault'); + const stopPropagationSpy = jest.spyOn(mouseDownEvent, 'stopPropagation'); + + detailedListItem.render.dispatchEvent(mouseDownEvent); + + expect(preventDefaultSpy).toHaveBeenCalled(); + expect(stopPropagationSpy).toHaveBeenCalled(); + }); + + it('should not call callbacks when they are not provided', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: basicListItem, + selectable: true, + clickable: true + }); + document.body.appendChild(detailedListItem.render); + + // Should not throw error + detailedListItem.render.click(); + expect(detailedListItem.render).toBeDefined(); + }); + }); + + describe('Focus Management', () => { + it('should set focus with target-command class', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + detailedListItem.setFocus(true, false); + + expect(detailedListItem.render.classList.contains('target-command')).toBe(true); + }); + + it('should remove focus by removing target-command class', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + detailedListItem.setFocus(true, false); + detailedListItem.setFocus(false, false); + + expect(detailedListItem.render.classList.contains('target-command')).toBe(false); + }); + + it('should scroll into view when requested', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const scrollIntoViewSpy = jest.spyOn(detailedListItem.render, 'scrollIntoView'); + + detailedListItem.setFocus(true, true); + + expect(scrollIntoViewSpy).toHaveBeenCalledWith(true); + }); + + it('should not scroll into view when not requested', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const scrollIntoViewSpy = jest.spyOn(detailedListItem.render, 'scrollIntoView'); + + detailedListItem.setFocus(true, false); + + expect(scrollIntoViewSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Item Retrieval', () => { + it('should return the list item', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + + const retrievedItem = detailedListItem.getItem(); + + expect(retrievedItem).toBe(basicListItem); + }); + }); + + describe('Attributes', () => { + it('should set correct attributes for enabled selectable item', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: basicListItem, + selectable: true, + clickable: false + }); + document.body.appendChild(detailedListItem.render); + + expect(detailedListItem.render.getAttribute('disabled')).toBe('false'); + expect(detailedListItem.render.getAttribute('selectable')).toBe('true'); + expect(detailedListItem.render.getAttribute('clickable')).toBe('false'); + }); + + it('should set correct attributes for disabled item', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: disabledItem, + selectable: true, + clickable: true + }); + document.body.appendChild(detailedListItem.render); + + expect(detailedListItem.render.getAttribute('disabled')).toBe('true'); + }); + + it('should set correct attributes for clickable item', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: basicListItem, + selectable: false, + clickable: true + }); + document.body.appendChild(detailedListItem.render); + + expect(detailedListItem.render.getAttribute('clickable')).toBe('true'); + }); + }); + + describe('Text Direction', () => { + it('should apply row text direction by default', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const textContainer = document.body.querySelector('.mynah-detailed-list-item-text'); + expect(textContainer?.classList.contains('mynah-detailed-list-item-text-direction-row')).toBe(true); + }); + + it('should apply column text direction when specified', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: basicListItem, + textDirection: 'column' + }); + document.body.appendChild(detailedListItem.render); + + const textContainer = document.body.querySelector('.mynah-detailed-list-item-text'); + expect(textContainer?.classList.contains('mynah-detailed-list-item-text-direction-column')).toBe(true); + }); + + it('should apply description text direction', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: basicListItem, + descriptionTextDirection: 'rtl' + }); + document.body.appendChild(detailedListItem.render); + + const description = document.body.querySelector('.mynah-detailed-list-item-description'); + expect(description?.classList.contains('rtl')).toBe(true); + }); + + it('should use ltr as default description text direction', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: basicListItem }); + document.body.appendChild(detailedListItem.render); + + const description = document.body.querySelector('.mynah-detailed-list-item-description'); + expect(description?.classList.contains('ltr')).toBe(true); + }); + }); + + describe('Edge Cases', () => { + it('should handle item without title or name', () => { + const itemWithoutTitle: DetailedListItem = { + id: 'no-title', + description: 'Only description' + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithoutTitle }); + document.body.appendChild(detailedListItem.render); + + const title = document.body.querySelector('.mynah-detailed-list-item-name'); + expect(title).toBeNull(); + }); + + it('should handle item without description', () => { + const itemWithoutDescription: DetailedListItem = { + id: 'no-desc', + title: 'Only title' + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithoutDescription }); + document.body.appendChild(detailedListItem.render); + + const description = document.body.querySelector('.mynah-detailed-list-item-description'); + expect(description).toBeNull(); + }); + + it('should handle empty actions array', () => { + const itemWithEmptyActions: DetailedListItem = { + id: 'empty-actions', + title: 'Empty Actions', + actions: [] + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithEmptyActions }); + document.body.appendChild(detailedListItem.render); + + // Empty actions array still creates the container but with no buttons + const actionsContainer = document.body.querySelector('.mynah-detailed-list-item-actions'); + if (actionsContainer != null) { + const buttons = actionsContainer.querySelectorAll('button'); + expect(buttons.length).toBe(0); + } else { + expect(actionsContainer).toBeNull(); + } + }); + + it('should handle markdown in description', () => { + const itemWithMarkdown: DetailedListItem = { + id: 'markdown', + title: 'Markdown Item', + description: '**Bold text** and *italic text*' + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithMarkdown }); + document.body.appendChild(detailedListItem.render); + + const description = document.body.querySelector('.mynah-detailed-list-item-description'); + expect(description?.innerHTML).toContain(''); + expect(description?.innerHTML).toContain(''); + }); + + it('should handle special characters in description', () => { + const itemWithSpecialChars: DetailedListItem = { + id: 'special-chars', + title: 'Special Characters', + description: 'Text with spaces and\nnew lines' + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithSpecialChars }); + document.body.appendChild(detailedListItem.render); + + const description = document.body.querySelector('.mynah-detailed-list-item-description'); + expect(description?.innerHTML).toContain(' '); + }); + }); + + describe('Tooltip Management', () => { + it('should hide tooltip when hideTooltip is called', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatus }); + + // Simulate showing tooltip + detailedListItem.hideTooltip(); + + // Should not throw error + expect(detailedListItem.render).toBeDefined(); + }); + + it('should clear timeout when hiding tooltip', () => { + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithStatus }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status') as HTMLElement; + + // Start showing tooltip + status.dispatchEvent(new MouseEvent('mouseover')); + + // Hide before timeout completes + detailedListItem.hideTooltip(); + + // Fast-forward timer + jest.advanceTimersByTime(350); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).not.toHaveBeenCalled(); + }); + + it('should not show tooltip when content is empty', () => { + const itemWithEmptyStatusDesc: DetailedListItem = { + id: 'empty-status-desc', + title: 'Item', + status: { + status: 'info', + text: 'Info' + // No description property at all + } + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithEmptyStatusDesc }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status') as HTMLElement; + + // Should not have mouseover event handler when no description + expect(status).toBeDefined(); + + // Manually trigger mouseover to test - should not create overlay + const mouseOverEvent = new MouseEvent('mouseover'); + Object.defineProperty(mouseOverEvent, 'currentTarget', { + value: status, + enumerable: true + }); + + // This should not trigger tooltip since there's no description + expect(status).toBeDefined(); + }); + + it('should handle tooltip with undefined content', () => { + const itemWithUndefinedStatusDesc: DetailedListItem = { + id: 'undefined-status-desc', + title: 'Item', + status: { + status: 'info', + text: 'Info', + description: undefined + } + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithUndefinedStatusDesc }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status') as HTMLElement; + + // Should not have mouseover event handler + expect(status).toBeDefined(); + }); + + it('should handle tooltip with whitespace-only content', () => { + const itemWithWhitespaceStatusDesc: DetailedListItem = { + id: 'whitespace-status-desc', + title: 'Item', + status: { + status: 'info', + text: 'Info', + description: ' \n\t ' // Only whitespace characters + } + }; + + detailedListItem = new DetailedListItemWrapper({ listItem: itemWithWhitespaceStatusDesc }); + document.body.appendChild(detailedListItem.render); + + const status = document.body.querySelector('.mynah-detailed-list-item-status') as HTMLElement; + + // Trigger mouseover + const mouseOverEvent = new MouseEvent('mouseover'); + Object.defineProperty(mouseOverEvent, 'currentTarget', { + value: status, + enumerable: true + }); + status.dispatchEvent(mouseOverEvent); + + // Fast-forward timer + jest.advanceTimersByTime(350); + + // The current implementation will create overlay even for whitespace-only content + // because it checks content.trim() !== undefined (which is always true) + // This is actually a bug in the implementation, but we test the current behavior + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + }); + + describe('Action Menu Overlay', () => { + it('should hide action menu overlay', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + // Show menu first + const menuButton = document.body.querySelector('[data-testid*="action-menu"]') as HTMLElement; + if (menuButton != null) { + menuButton.click(); + + // Now test hiding - this should be called internally when action is clicked + const actionButton = document.body.querySelector('.mynah-detailed-list-item-actions button') as HTMLElement; + if (actionButton != null) { + actionButton.click(); + expect(mockOnActionClick).toHaveBeenCalled(); + } + } + }); + + it('should handle action button creation with all properties', () => { + const itemWithComplexAction: DetailedListItem = { + id: 'complex-action', + title: 'Complex Action Item', + actions: [ + { + id: 'complex-action-1', + text: 'Complex Action', + icon: MynahIcons.PENCIL, + description: 'This is a complex action', + disabled: false, + status: 'warning', + confirmation: { + title: 'Confirm Action', + description: 'Are you sure?', + confirmButtonText: 'Yes', + cancelButtonText: 'No' + } + } + ], + groupActions: false + }; + + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithComplexAction, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + const actionButton = document.body.querySelector('.mynah-detailed-list-item-actions button') as HTMLElement; + expect(actionButton).toBeDefined(); + + // The button should be created with all properties, but clicking might not trigger + // the callback due to confirmation dialog or other button component behavior + expect(actionButton).toBeDefined(); + }); + + it('should create action menu overlay with correct structure', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + // Trigger the menu button click to create overlay + const menuButton = document.body.querySelector('[data-testid*="action-menu"]') as HTMLElement; + if (menuButton != null) { + menuButton.click(); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).toHaveBeenCalledWith(expect.objectContaining({ + background: true, + closeOnOutsideClick: true, + dimOutside: false, + removeOtherOverlays: true, + children: expect.arrayContaining([ + expect.objectContaining({ + type: 'div', + classNames: [ 'mynah-detailed-list-item-actions-overlay' ] + }) + ]) + })); + } + }); + + it('should trigger menu button click and show overlay', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + // Find the menu button (ellipsis button) + const menuButton = document.body.querySelector('[data-testid*="action-menu"]') as HTMLElement; + + if (menuButton != null) { + // Click the menu button to trigger the overlay + const clickEvent = new MouseEvent('click', { bubbles: true }); + menuButton.dispatchEvent(clickEvent); + + // Verify overlay was created + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + } else { + // If no menu button, actions are rendered individually + const actionButtons = document.body.querySelectorAll('.mynah-detailed-list-item-actions button'); + expect(actionButtons.length).toBeGreaterThan(0); + } + }); + + it('should handle action menu with no actions', () => { + const itemWithNoActions: DetailedListItem = { + id: 'no-actions', + title: 'No Actions Item' + }; + + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithNoActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + const actionsContainer = document.body.querySelector('.mynah-detailed-list-item-actions'); + expect(actionsContainer).toBeNull(); + }); + + it('should handle single action without grouping', () => { + const itemWithSingleAction: DetailedListItem = { + id: 'single-action', + title: 'Single Action Item', + actions: [ + { + id: 'single-action-1', + text: 'Single Action', + icon: MynahIcons.PENCIL + } + ] + }; + + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithSingleAction, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + // Should render as individual button, not menu + const actionButton = document.body.querySelector('.mynah-detailed-list-item-actions button') as HTMLElement; + expect(actionButton).toBeDefined(); + + const menuButton = document.body.querySelector('[data-testid*="action-menu"]'); + expect(menuButton).toBeNull(); + }); + + it('should handle action menu overlay close', () => { + detailedListItem = new DetailedListItemWrapper({ + listItem: itemWithActions, + onActionClick: mockOnActionClick + }); + document.body.appendChild(detailedListItem.render); + + // Show the menu first + const menuButton = document.body.querySelector('[data-testid*="action-menu"]') as HTMLElement; + if (menuButton != null) { + menuButton.click(); + + // Now simulate clicking an action in the overlay which should close it + // This tests the hideActionMenuOverlay method + expect(detailedListItem.render).toBeDefined(); + } + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list-sheet.spec.ts b/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list-sheet.spec.ts new file mode 100644 index 00000000..95265d44 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list-sheet.spec.ts @@ -0,0 +1,627 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DetailedListSheet, DetailedListSheetProps } from '../../../components/detailed-list/detailed-list-sheet'; +import { DetailedList, ChatItemButton, MynahEventNames } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { MynahIcons } from '../../../components/icon'; + +// Mock the global events +jest.mock('../../../helper/events', () => ({ + MynahUIGlobalEvents: { + getInstance: jest.fn(() => ({ + dispatch: jest.fn() + })) + } +})); + +// Mock the detailed list wrapper +jest.mock('../../../components/detailed-list/detailed-list', () => ({ + DetailedListWrapper: jest.fn().mockImplementation(() => ({ + render: document.createElement('div'), + update: jest.fn() + })) +})); + +describe('DetailedListSheet Component', () => { + let detailedListSheet: DetailedListSheet; + let mockDispatch: jest.Mock; + let mockOnFilterValueChange: jest.Mock; + let mockOnKeyPress: jest.Mock; + let mockOnItemSelect: jest.Mock; + let mockOnItemClick: jest.Mock; + let mockOnBackClick: jest.Mock; + let mockOnTitleActionClick: jest.Mock; + let mockOnActionClick: jest.Mock; + let mockOnFilterActionClick: jest.Mock; + let mockOnClose: jest.Mock; + + const basicDetailedList: DetailedList = { + header: { + title: 'Test Sheet', + description: 'Test sheet description', + icon: MynahIcons.INFO + }, + list: [ + { + groupName: 'Test Group', + children: [ + { + id: 'item-1', + title: 'Test Item 1', + description: 'Description 1' + } + ] + } + ] + }; + + const detailedListWithActions: DetailedList = { + header: { + title: 'Sheet with Actions', + description: 'Sheet with header actions', + actions: [ + { + id: 'header-action-1', + text: 'Header Action', + icon: MynahIcons.PENCIL + } + ] + }, + list: basicDetailedList.list + }; + + const detailedListWithStatus: DetailedList = { + header: { + title: 'Sheet with Status', + description: 'Sheet with status', + status: { + status: 'warning', + title: 'Warning Status', + description: 'This is a warning', + icon: MynahIcons.WARNING + } + }, + list: basicDetailedList.list + }; + + beforeEach(() => { + mockDispatch = jest.fn(); + mockOnFilterValueChange = jest.fn(); + mockOnKeyPress = jest.fn(); + mockOnItemSelect = jest.fn(); + mockOnItemClick = jest.fn(); + mockOnBackClick = jest.fn(); + mockOnTitleActionClick = jest.fn(); + mockOnActionClick = jest.fn(); + mockOnFilterActionClick = jest.fn(); + mockOnClose = jest.fn(); + + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue({ + dispatch: mockDispatch + }); + + jest.clearAllMocks(); + + // Mock window event listeners + global.addEventListener = jest.fn(); + global.removeEventListener = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Basic Functionality', () => { + it('should create detailed list sheet with basic props', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + expect(detailedListSheet).toBeDefined(); + expect(detailedListSheet.props).toBeDefined(); + expect(detailedListSheet.detailedListWrapper).toBeDefined(); + }); + + it('should create detailed list wrapper without header', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const { DetailedListWrapper } = jest.requireMock('../../../components/detailed-list/detailed-list'); + expect(DetailedListWrapper).toHaveBeenCalledWith( + expect.objectContaining({ + detailedList: expect.objectContaining({ + header: undefined + }) + }) + ); + }); + + it('should pass event handlers to detailed list wrapper', () => { + const props: DetailedListSheetProps = { + detailedList: basicDetailedList, + events: { + onFilterValueChange: mockOnFilterValueChange, + onItemSelect: mockOnItemSelect, + onItemClick: mockOnItemClick, + onActionClick: mockOnActionClick, + onFilterActionClick: mockOnFilterActionClick + } + }; + + detailedListSheet = new DetailedListSheet(props); + + const { DetailedListWrapper } = jest.requireMock('../../../components/detailed-list/detailed-list'); + expect(DetailedListWrapper).toHaveBeenCalledWith( + expect.objectContaining({ + onFilterValueChange: mockOnFilterValueChange, + onItemSelect: mockOnItemSelect, + onItemClick: mockOnItemClick, + onItemActionClick: mockOnActionClick, + onFilterActionClick: mockOnFilterActionClick + }) + ); + }); + + it('should store props reference', () => { + const props: DetailedListSheetProps = { + detailedList: basicDetailedList, + events: { + onKeyPress: mockOnKeyPress + } + }; + + detailedListSheet = new DetailedListSheet(props); + + expect(detailedListSheet.props).toBe(props); + }); + }); + + describe('Sheet Opening', () => { + it('should dispatch open sheet event', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + detailedListSheet.open(); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.OPEN_SHEET, expect.objectContaining({ + fullScreen: true, + title: basicDetailedList.header?.title, + description: basicDetailedList.header?.description, + children: expect.any(Array) + })); + }); + + it('should dispatch open sheet with back button when requested', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + detailedListSheet.open(true); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.OPEN_SHEET, expect.objectContaining({ + showBackButton: true + })); + }); + + it('should dispatch open sheet with header actions', () => { + detailedListSheet = new DetailedListSheet({ detailedList: detailedListWithActions }); + + detailedListSheet.open(); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.OPEN_SHEET, expect.objectContaining({ + actions: detailedListWithActions.header?.actions + })); + }); + + it('should dispatch open sheet with status', () => { + detailedListSheet = new DetailedListSheet({ detailedList: detailedListWithStatus }); + + detailedListSheet.open(); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.OPEN_SHEET, expect.objectContaining({ + status: detailedListWithStatus.header?.status + })); + }); + + it('should add keydown event listener', () => { + detailedListSheet = new DetailedListSheet({ + detailedList: basicDetailedList, + events: { onKeyPress: mockOnKeyPress } + }); + + detailedListSheet.open(); + + expect(global.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); + + it('should handle sheet close callback', () => { + detailedListSheet = new DetailedListSheet({ + detailedList: basicDetailedList, + events: { onClose: mockOnClose } + }); + + detailedListSheet.open(); + + // Get the onClose callback from the dispatch call + const dispatchCall = mockDispatch.mock.calls[0]; + const sheetProps = dispatchCall[1]; + + // Simulate sheet close + sheetProps.onClose(); + + expect(mockOnClose).toHaveBeenCalled(); + expect(global.removeEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); + + it('should handle title action click', () => { + detailedListSheet = new DetailedListSheet({ + detailedList: detailedListWithActions, + events: { onTitleActionClick: mockOnTitleActionClick } + }); + + detailedListSheet.open(); + + // Get the onActionClick callback from the dispatch call + const dispatchCall = mockDispatch.mock.calls[0]; + const sheetProps = dispatchCall[1]; + + const testAction: ChatItemButton = { id: 'test-action', text: 'Test' }; + sheetProps.onActionClick(testAction); + + expect(mockOnTitleActionClick).toHaveBeenCalledWith(testAction); + }); + + it('should handle back button click', () => { + detailedListSheet = new DetailedListSheet({ + detailedList: basicDetailedList, + events: { onBackClick: mockOnBackClick } + }); + + detailedListSheet.open(); + + // Get the onBack callback from the dispatch call + const dispatchCall = mockDispatch.mock.calls[0]; + const sheetProps = dispatchCall[1]; + + sheetProps.onBack(); + + expect(mockOnBackClick).toHaveBeenCalled(); + }); + }); + + describe('Keyboard Event Handling', () => { + it('should handle keydown events', () => { + detailedListSheet = new DetailedListSheet({ + detailedList: basicDetailedList, + events: { onKeyPress: mockOnKeyPress } + }); + + detailedListSheet.open(); + + // Get the keydown handler from addEventListener call + const addEventListenerCall = (global.addEventListener as jest.Mock).mock.calls.find( + call => call[0] === 'keydown' + ); + const keydownHandler = addEventListenerCall[1]; + + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter' }); + keydownHandler(keyEvent); + + expect(mockOnKeyPress).toHaveBeenCalledWith(keyEvent); + }); + + it('should not throw error when onKeyPress is not provided', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + detailedListSheet.open(); + + // Get the keydown handler + const addEventListenerCall = (global.addEventListener as jest.Mock).mock.calls.find( + call => call[0] === 'keydown' + ); + const keydownHandler = addEventListenerCall[1]; + + const keyEvent = new KeyboardEvent('keydown', { key: 'Escape' }); + + // Should not throw error + expect(() => keydownHandler(keyEvent)).not.toThrow(); + }); + }); + + describe('Sheet Updates', () => { + it('should update sheet header', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + header: { + title: 'Updated Title', + description: 'Updated description' + } + }; + + detailedListSheet.update(updatedList); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.UPDATE_SHEET, expect.objectContaining({ + title: 'Updated Title', + description: 'Updated description' + })); + }); + + it('should update sheet with back button', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + header: { + title: 'Updated Title' + } + }; + + detailedListSheet.update(updatedList, true); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.UPDATE_SHEET, expect.objectContaining({ + showBackButton: true + })); + }); + + it('should update sheet with status', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + header: { + title: 'Updated Title', + status: { + status: 'error', + title: 'Error Status', + description: 'Something went wrong' + } + } + }; + + detailedListSheet.update(updatedList); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.UPDATE_SHEET, expect.objectContaining({ + status: updatedList.header?.status + })); + }); + + it('should update sheet with actions', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + header: { + title: 'Updated Title', + actions: [ + { id: 'new-action', text: 'New Action', icon: MynahIcons.REFRESH } + ] + } + }; + + detailedListSheet.update(updatedList); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.UPDATE_SHEET, expect.objectContaining({ + actions: updatedList.header?.actions + })); + }); + + it('should update detailed list wrapper', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + header: { + title: 'Updated Title' + }, + list: [ + { + groupName: 'Updated Group', + children: [ + { id: 'new-item', title: 'New Item' } + ] + } + ] + }; + + detailedListSheet.update(updatedList); + + expect(detailedListSheet.detailedListWrapper.update).toHaveBeenCalledWith( + expect.objectContaining({ + header: undefined, // Header should be removed for wrapper + list: updatedList.list + }) + ); + }); + + it('should merge props when updating', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const partialUpdate: DetailedList = { + list: [ + { + groupName: 'New Group', + children: [ { id: 'new-item', title: 'New Item' } ] + } + ] + }; + + detailedListSheet.update(partialUpdate); + + // Should merge with existing props + expect(detailedListSheet.props.detailedList.header?.title).toBe(basicDetailedList.header?.title); + expect(detailedListSheet.props.detailedList.list).toBe(partialUpdate.list); + }); + + it('should not dispatch update sheet when header is not provided', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + list: [ + { + groupName: 'Updated Group', + children: [ { id: 'item', title: 'Item' } ] + } + ] + }; + + detailedListSheet.update(updatedList); + + // Should only call wrapper update, not sheet update + expect(mockDispatch).not.toHaveBeenCalledWith(MynahEventNames.UPDATE_SHEET, expect.anything()); + }); + + it('should handle update with null header', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + header: null as any, + list: [ + { + groupName: 'Updated Group', + children: [ { id: 'item', title: 'Item' } ] + } + ] + }; + + detailedListSheet.update(updatedList); + + // Should not dispatch sheet update for null header + expect(mockDispatch).not.toHaveBeenCalledWith(MynahEventNames.UPDATE_SHEET, expect.anything()); + }); + + it('should handle update with undefined showBackButton', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + header: { + title: 'Updated Title' + } + }; + + detailedListSheet.update(updatedList, undefined); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.UPDATE_SHEET, expect.objectContaining({ + showBackButton: undefined + })); + }); + }); + + describe('Sheet Closing', () => { + it('should dispatch close sheet event', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + detailedListSheet.close(); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.CLOSE_SHEET); + }); + }); + + describe('Edge Cases', () => { + it('should handle detailed list without header', () => { + const listWithoutHeader: DetailedList = { + list: [ + { + groupName: 'Group', + children: [ { id: 'item', title: 'Item' } ] + } + ] + }; + + detailedListSheet = new DetailedListSheet({ detailedList: listWithoutHeader }); + + detailedListSheet.open(); + + expect(mockDispatch).toHaveBeenCalledWith(MynahEventNames.OPEN_SHEET, expect.objectContaining({ + title: undefined, + description: undefined + })); + }); + + it('should handle empty events object', () => { + detailedListSheet = new DetailedListSheet({ + detailedList: basicDetailedList, + events: {} + }); + + detailedListSheet.open(); + + // Should not throw error + expect(detailedListSheet).toBeDefined(); + }); + + it('should handle undefined events', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + detailedListSheet.open(); + + // Should not throw error + expect(detailedListSheet).toBeDefined(); + }); + + it('should handle update without header', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + const updatedList: DetailedList = { + list: [ { groupName: 'Group', children: [] } ] + }; + + detailedListSheet.update(updatedList); + + // Should only update wrapper + expect(detailedListSheet.detailedListWrapper.update).toHaveBeenCalled(); + }); + + it('should handle empty update', () => { + detailedListSheet = new DetailedListSheet({ detailedList: basicDetailedList }); + + detailedListSheet.update({}); + + // Should not throw error + expect(detailedListSheet).toBeDefined(); + }); + }); + + describe('Legacy Support', () => { + it('should handle tabId prop for backwards compatibility', () => { + const propsWithTabId: DetailedListSheetProps = { + tabId: 'legacy-tab-id', + detailedList: basicDetailedList + }; + + detailedListSheet = new DetailedListSheet(propsWithTabId); + + expect(detailedListSheet.props.tabId).toBe('legacy-tab-id'); + }); + }); + + describe('Event Handler Integration', () => { + it('should handle all event types', () => { + const allEvents: DetailedListSheetProps['events'] = { + onFilterValueChange: mockOnFilterValueChange, + onKeyPress: mockOnKeyPress, + onItemSelect: mockOnItemSelect, + onItemClick: mockOnItemClick, + onBackClick: mockOnBackClick, + onTitleActionClick: mockOnTitleActionClick, + onActionClick: mockOnActionClick, + onFilterActionClick: mockOnFilterActionClick, + onClose: mockOnClose + }; + + detailedListSheet = new DetailedListSheet({ + detailedList: detailedListWithActions, + events: allEvents + }); + + detailedListSheet.open(); + + // Verify all handlers are properly set up + const { DetailedListWrapper } = jest.requireMock('../../../components/detailed-list/detailed-list'); + expect(DetailedListWrapper).toHaveBeenCalledWith( + expect.objectContaining({ + onFilterValueChange: mockOnFilterValueChange, + onItemSelect: mockOnItemSelect, + onItemClick: mockOnItemClick, + onItemActionClick: mockOnActionClick, + onFilterActionClick: mockOnFilterActionClick + }) + ); + + expect(global.addEventListener).toHaveBeenCalledWith('keydown', expect.any(Function)); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list.spec.ts b/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list.spec.ts new file mode 100644 index 00000000..61b3a8e5 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/detailed-list/detailed-list.spec.ts @@ -0,0 +1,922 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DetailedListWrapper } from '../../../components/detailed-list/detailed-list'; +import { DetailedList } from '../../../static'; +import { MynahIcons } from '../../../components/icon'; + +// Mock the form items wrapper +jest.mock('../../../components/chat-item/chat-item-form-items', () => ({ + ChatItemFormItemsWrapper: jest.fn().mockImplementation(() => ({ + render: document.createElement('div'), + getAllValues: jest.fn(() => ({ filter1: 'value1' })), + isFormValid: jest.fn(() => true) + })) +})); + +describe('DetailedListWrapper Component', () => { + let detailedListWrapper: DetailedListWrapper; + let mockOnFilterValueChange: jest.Mock; + let mockOnGroupActionClick: jest.Mock; + let mockOnGroupClick: jest.Mock; + let mockOnItemSelect: jest.Mock; + let mockOnItemClick: jest.Mock; + let mockOnFilterActionClick: jest.Mock; + + const basicDetailedList: DetailedList = { + list: [ + { + groupName: 'Test Group', + children: [ + { + id: 'item-1', + title: 'Test Item 1', + description: 'Description 1' + }, + { + id: 'item-2', + title: 'Test Item 2', + description: 'Description 2' + } + ] + } + ] + }; + + const detailedListWithHeader: DetailedList = { + header: { + title: 'Test Header', + description: 'Test header description', + icon: MynahIcons.INFO, + status: { + status: 'success', + title: 'Success Status', + description: 'Everything is working', + icon: MynahIcons.OK + } + }, + list: basicDetailedList.list + }; + + const detailedListWithFilters: DetailedList = { + filterOptions: [ + { + id: 'filter1', + type: 'select', + title: 'Filter 1', + options: [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' } + ] + } + ], + filterActions: [ + { + id: 'apply-filter', + text: 'Apply', + icon: MynahIcons.OK + } + ], + list: basicDetailedList.list + }; + + const detailedListWithGroupActions: DetailedList = { + list: [ + { + groupName: 'Group with Actions', + icon: MynahIcons.FOLDER, + actions: [ + { + id: 'group-action-1', + text: 'Group Action', + icon: MynahIcons.PENCIL + } + ], + children: [ + { + id: 'item-1', + title: 'Item 1', + description: 'Description 1' + } + ] + } + ] + }; + + const largeDetailedList: DetailedList = { + list: [ + { + groupName: 'Large Group', + children: Array.from({ length: 250 }, (_, i) => ({ + id: `item-${i}`, + title: `Item ${i}`, + description: `Description ${i}` + })) + } + ] + }; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnFilterValueChange = jest.fn(); + mockOnGroupActionClick = jest.fn(); + mockOnGroupClick = jest.fn(); + mockOnItemSelect = jest.fn(); + mockOnItemClick = jest.fn(); + mockOnFilterActionClick = jest.fn(); + jest.clearAllMocks(); + + // Mock scrollIntoView + Element.prototype.scrollIntoView = jest.fn(); + + // Mock requestAnimationFrame + global.requestAnimationFrame = jest.fn((cb) => { + const timestamp = 0; + cb(timestamp); + return 0; + }); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('Basic Functionality', () => { + it('should create detailed list wrapper with basic props', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + + expect(detailedListWrapper.render).toBeDefined(); + expect(detailedListWrapper.render.classList.contains('mynah-detailed-list')).toBe(true); + }); + + it('should have correct test ID', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const wrapper = document.body.querySelector('[data-testid*="quick-picks-wrapper"]'); + expect(wrapper).toBeDefined(); + }); + + it('should render list items', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const items = document.body.querySelectorAll('.mynah-detailed-list-item'); + expect(items.length).toBe(2); + }); + + it('should render group names', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const groupTitle = document.body.querySelector('.mynah-detailed-list-group-title'); + expect(groupTitle?.textContent).toContain('Test Group'); + }); + + it('should have proper component structure', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const header = document.body.querySelector('.mynah-detailed-list-header-wrapper'); + const filters = document.body.querySelector('.mynah-detailed-list-filters-wrapper'); + const groups = document.body.querySelector('.mynah-detailed-list-item-groups-wrapper'); + const filterActions = document.body.querySelector('.mynah-detailed-list-filter-actions-wrapper'); + + expect(header).toBeDefined(); + expect(filters).toBeDefined(); + expect(groups).toBeDefined(); + expect(filterActions).toBeDefined(); + }); + }); + + describe('Header Rendering', () => { + it('should render header when provided', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: detailedListWithHeader }); + document.body.appendChild(detailedListWrapper.render); + + const headerWrapper = document.body.querySelector('.mynah-detailed-list-header-wrapper'); + expect(headerWrapper).toBeDefined(); + expect(headerWrapper?.textContent).toContain('Test Header'); + }); + + it('should render header description', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: detailedListWithHeader }); + document.body.appendChild(detailedListWrapper.render); + + const headerWrapper = document.body.querySelector('.mynah-detailed-list-header-wrapper'); + expect(headerWrapper?.textContent).toContain('Test header description'); + }); + + it('should render header status when provided', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: detailedListWithHeader }); + document.body.appendChild(detailedListWrapper.render); + + const statusCard = document.body.querySelector('[data-testid*="sheet-description"]'); + expect(statusCard).toBeDefined(); + }); + + it('should not render header when not provided', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const headerWrapper = document.body.querySelector('.mynah-detailed-list-header-wrapper'); + expect(headerWrapper?.textContent?.trim()).toBe(''); + }); + + it('should handle header without status', () => { + const headerWithoutStatus: DetailedList = { + header: { + title: 'Header Without Status', + description: 'Just description', + icon: MynahIcons.INFO + }, + list: basicDetailedList.list + }; + + detailedListWrapper = new DetailedListWrapper({ detailedList: headerWithoutStatus }); + document.body.appendChild(detailedListWrapper.render); + + const headerWrapper = document.body.querySelector('.mynah-detailed-list-header-wrapper'); + expect(headerWrapper?.textContent).toContain('Header Without Status'); + + // Should not have status card + const statusCard = document.body.querySelector('[data-testid*="sheet-description"]'); + expect(statusCard).toBeNull(); + }); + + it('should handle header with null status', () => { + const headerWithNullStatus: DetailedList = { + header: { + title: 'Header With Null Status', + description: 'Description', + status: null as any + }, + list: basicDetailedList.list + }; + + detailedListWrapper = new DetailedListWrapper({ detailedList: headerWithNullStatus }); + document.body.appendChild(detailedListWrapper.render); + + const headerWrapper = document.body.querySelector('.mynah-detailed-list-header-wrapper'); + expect(headerWrapper?.textContent).toContain('Header With Null Status'); + }); + }); + + describe('Filter Rendering', () => { + it('should render filters when provided', () => { + detailedListWrapper = new DetailedListWrapper({ + detailedList: detailedListWithFilters, + onFilterValueChange: mockOnFilterValueChange + }); + document.body.appendChild(detailedListWrapper.render); + + const filtersWrapper = document.body.querySelector('.mynah-detailed-list-filters-wrapper'); + expect(filtersWrapper).toBeDefined(); + expect(filtersWrapper?.children.length).toBeGreaterThan(0); + }); + + it('should render filter actions when provided', () => { + detailedListWrapper = new DetailedListWrapper({ + detailedList: detailedListWithFilters, + onFilterActionClick: mockOnFilterActionClick + }); + document.body.appendChild(detailedListWrapper.render); + + const filterActionsWrapper = document.body.querySelector('.mynah-detailed-list-filter-actions-wrapper'); + expect(filterActionsWrapper).toBeDefined(); + }); + + it('should not render filters when not provided', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const filtersWrapper = document.body.querySelector('.mynah-detailed-list-filters-wrapper'); + expect(filtersWrapper?.textContent?.trim()).toBe(''); + }); + + it('should handle filter action click', () => { + detailedListWrapper = new DetailedListWrapper({ + detailedList: detailedListWithFilters, + onFilterActionClick: mockOnFilterActionClick + }); + document.body.appendChild(detailedListWrapper.render); + + // Simulate filter action click through the button wrapper + // This would normally be triggered by the ChatItemButtonsWrapper + expect(detailedListWrapper.render).toBeDefined(); + }); + }); + + describe('Group Functionality', () => { + it('should render group with icon', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: detailedListWithGroupActions }); + document.body.appendChild(detailedListWrapper.render); + + const groupTitle = document.body.querySelector('.mynah-detailed-list-group-title'); + const icon = groupTitle?.querySelector('.mynah-icon'); + expect(icon).toBeDefined(); + }); + + it('should render group actions', () => { + detailedListWrapper = new DetailedListWrapper({ + detailedList: detailedListWithGroupActions, + onGroupActionClick: mockOnGroupActionClick + }); + document.body.appendChild(detailedListWrapper.render); + + const groupTitle = document.body.querySelector('.mynah-detailed-list-group-title'); + expect(groupTitle).toBeDefined(); + }); + + it('should handle group click when clickable', () => { + const clickableList: DetailedList = { + ...detailedListWithGroupActions, + selectable: 'clickable' + }; + + detailedListWrapper = new DetailedListWrapper({ + detailedList: clickableList, + onGroupClick: mockOnGroupClick + }); + document.body.appendChild(detailedListWrapper.render); + + const groupTitle = document.body.querySelector('.mynah-detailed-list-group-title') as HTMLElement; + expect(groupTitle?.classList.contains('mynah-group-title-clickable')).toBe(true); + + groupTitle.click(); + expect(mockOnGroupClick).toHaveBeenCalledWith('Group with Actions'); + }); + + it('should not make group clickable when selectable is not clickable', () => { + detailedListWrapper = new DetailedListWrapper({ + detailedList: detailedListWithGroupActions, + onGroupClick: mockOnGroupClick + }); + document.body.appendChild(detailedListWrapper.render); + + const groupTitle = document.body.querySelector('.mynah-detailed-list-group-title'); + expect(groupTitle?.classList.contains('mynah-group-title-clickable')).toBe(false); + }); + + it('should render groups without names', () => { + const listWithoutGroupName: DetailedList = { + list: [ + { + children: [ + { id: 'item-1', title: 'Item 1' } + ] + } + ] + }; + + detailedListWrapper = new DetailedListWrapper({ detailedList: listWithoutGroupName }); + document.body.appendChild(detailedListWrapper.render); + + const groupTitle = document.body.querySelector('.mynah-detailed-list-group-title'); + expect(groupTitle).toBeNull(); + + const items = document.body.querySelectorAll('.mynah-detailed-list-item'); + expect(items.length).toBe(1); + }); + + it('should handle indented children', () => { + const listWithIndentedChildren: DetailedList = { + list: [ + { + groupName: 'Parent Group', + childrenIndented: true, + children: [ + { id: 'item-1', title: 'Indented Item' } + ] + } + ] + }; + + detailedListWrapper = new DetailedListWrapper({ detailedList: listWithIndentedChildren }); + document.body.appendChild(detailedListWrapper.render); + + const itemsBlock = document.body.querySelector('.mynah-detailed-list-items-block'); + expect(itemsBlock?.classList.contains('indented')).toBe(true); + }); + }); + + describe('Virtualization', () => { + it('should create blocks for large lists', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const itemsBlocks = document.body.querySelectorAll('.mynah-detailed-list-items-block'); + expect(itemsBlocks.length).toBeGreaterThan(1); // Should be chunked into multiple blocks + }); + + it('should set minimum height for virtualized blocks', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const itemsBlocks = document.body.querySelectorAll('.mynah-detailed-list-items-block'); + const firstBlock = itemsBlocks[0] as HTMLElement; + expect(firstBlock.style.minHeight).toBeTruthy(); + }); + + it('should handle scroll events for virtualization', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const groupsContainer = document.body.querySelector('.mynah-detailed-list-item-groups-wrapper') as HTMLElement; + + // Mock scroll properties + Object.defineProperty(groupsContainer, 'offsetHeight', { value: 400 }); + Object.defineProperty(groupsContainer, 'scrollTop', { value: 200 }); + + // Trigger scroll event + groupsContainer.dispatchEvent(new Event('scroll')); + + // Should not throw error + expect(detailedListWrapper.render).toBeDefined(); + }); + + it('should render first 5 blocks immediately', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const itemsBlocks = document.body.querySelectorAll('.mynah-detailed-list-items-block'); + let renderedBlocks = 0; + + itemsBlocks.forEach((block, index) => { + if (block.children.length > 0) { + renderedBlocks++; + } + }); + + expect(renderedBlocks).toBeGreaterThan(0); + }); + + it('should handle virtualization with blocks entering viewport', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const groupsContainer = document.body.querySelector('.mynah-detailed-list-item-groups-wrapper') as HTMLElement; + const itemsBlocks = document.body.querySelectorAll('.mynah-detailed-list-items-block'); + + // Mock properties for a block that should be rendered + Object.defineProperty(groupsContainer, 'offsetHeight', { value: 400 }); + Object.defineProperty(groupsContainer, 'scrollTop', { value: 0 }); + + // Mock a block that's in viewport but not rendered + const testBlock = itemsBlocks[6] as HTMLElement; // Beyond first 5 + if (testBlock != null) { + Object.defineProperty(testBlock, 'offsetTop', { value: 100 }); + Object.defineProperty(testBlock, 'offsetHeight', { value: 200 }); + + // Clear the block first + testBlock.innerHTML = ''; + + // Trigger scroll to render it + groupsContainer.dispatchEvent(new Event('scroll')); + + expect(detailedListWrapper.render).toBeDefined(); + } + }); + + it('should handle virtualization with blocks leaving viewport', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const groupsContainer = document.body.querySelector('.mynah-detailed-list-item-groups-wrapper') as HTMLElement; + const itemsBlocks = document.body.querySelectorAll('.mynah-detailed-list-items-block'); + + // Mock properties for a block that should be cleared + Object.defineProperty(groupsContainer, 'offsetHeight', { value: 400 }); + Object.defineProperty(groupsContainer, 'scrollTop', { value: 2000 }); // Scrolled far down + + // Mock a block that's out of viewport + const testBlock = itemsBlocks[0] as HTMLElement; + if (testBlock != null) { + Object.defineProperty(testBlock, 'offsetTop', { value: 0 }); + Object.defineProperty(testBlock, 'offsetHeight', { value: 200 }); + + // Trigger scroll to clear it + groupsContainer.dispatchEvent(new Event('scroll')); + + expect(detailedListWrapper.render).toBeDefined(); + } + }); + }); + + describe('Item Selection and Navigation', () => { + it('should handle item selection', () => { + detailedListWrapper = new DetailedListWrapper({ + detailedList: basicDetailedList, + onItemSelect: mockOnItemSelect + }); + document.body.appendChild(detailedListWrapper.render); + + const firstItem = document.body.querySelector('.mynah-detailed-list-item') as HTMLElement; + firstItem.click(); + + expect(mockOnItemSelect).toHaveBeenCalled(); + }); + + it('should handle item click', () => { + const clickableList: DetailedList = { + ...basicDetailedList, + selectable: 'clickable' + }; + + detailedListWrapper = new DetailedListWrapper({ + detailedList: clickableList, + onItemClick: mockOnItemClick + }); + document.body.appendChild(detailedListWrapper.render); + + const firstItem = document.body.querySelector('.mynah-detailed-list-item') as HTMLElement; + firstItem.click(); + + expect(mockOnItemClick).toHaveBeenCalled(); + }); + + it('should change target up', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + // Should not throw error + detailedListWrapper.changeTarget('up'); + expect(detailedListWrapper.render).toBeDefined(); + }); + + it('should change target down', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + detailedListWrapper.changeTarget('down'); + expect(detailedListWrapper.render).toBeDefined(); + }); + + it('should change target with snap on last and first', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + detailedListWrapper.changeTarget('up', true); + detailedListWrapper.changeTarget('down', true); + expect(detailedListWrapper.render).toBeDefined(); + }); + + it('should change target with scroll into view', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + detailedListWrapper.changeTarget('down', false, true); + expect(detailedListWrapper.render).toBeDefined(); + }); + + it('should get target element', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + detailedListWrapper.changeTarget('down', false, true); + + const targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement).toBeDefined(); + expect(targetElement?.id).toBe('item-1'); + }); + + it('should return null when no selectable elements', () => { + const emptyList: DetailedList = { list: [] }; + detailedListWrapper = new DetailedListWrapper({ detailedList: emptyList }); + + const targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement).toBeNull(); + }); + + it('should handle navigation with disabled items', () => { + const listWithDisabledItems: DetailedList = { + list: [ + { + groupName: 'Mixed Group', + children: [ + { id: 'item-1', title: 'Enabled Item' }, + { id: 'item-2', title: 'Disabled Item', disabled: true }, + { id: 'item-3', title: 'Another Enabled Item' } + ] + } + ] + }; + + detailedListWrapper = new DetailedListWrapper({ detailedList: listWithDisabledItems }); + document.body.appendChild(detailedListWrapper.render); + + // Should only include enabled items in navigation + detailedListWrapper.changeTarget('down'); + const targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement?.disabled).not.toBe(true); + }); + + it('should handle navigation wrapping from first to last', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + // First select the first item + detailedListWrapper.changeTarget('down'); + + // Go up from first item (should wrap to last) + detailedListWrapper.changeTarget('up', false); + const targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement?.id).toBe('item-2'); // Should wrap to last item + }); + + it('should handle navigation wrapping from last to first', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + // First select an item + detailedListWrapper.changeTarget('down'); + // Move to last item + detailedListWrapper.changeTarget('down'); + // Then go down (should wrap to first) + detailedListWrapper.changeTarget('down', false); + const targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement?.id).toBe('item-1'); // Should wrap to first item + }); + + it('should handle navigation with snap at boundaries', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + // First select the first item + detailedListWrapper.changeTarget('down'); + + // Go up with snap (should stay at first) + detailedListWrapper.changeTarget('up', true); + let targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement?.id).toBe('item-1'); + + // Move to last item + detailedListWrapper.changeTarget('down'); + // Go down with snap (should stay at last) + detailedListWrapper.changeTarget('down', true); + targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement?.id).toBe('item-2'); + }); + }); + + describe('Update Functionality', () => { + it('should update header', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const updatedList: DetailedList = { + header: { + title: 'Updated Header', + description: 'Updated description' + } + }; + + detailedListWrapper.update(updatedList); + + const headerWrapper = document.body.querySelector('.mynah-detailed-list-header-wrapper'); + expect(headerWrapper?.textContent).toContain('Updated Header'); + }); + + it('should update filters', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const updatedList: DetailedList = { + filterOptions: [ + { + id: 'new-filter', + type: 'textinput', + title: 'New Filter' + } + ] + }; + + detailedListWrapper.update(updatedList); + + const filtersWrapper = document.body.querySelector('.mynah-detailed-list-filters-wrapper'); + expect(filtersWrapper).toBeDefined(); + }); + + it('should update filter actions', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const updatedList: DetailedList = { + filterActions: [ + { + id: 'new-action', + text: 'New Action', + icon: MynahIcons.REFRESH + } + ] + }; + + detailedListWrapper.update(updatedList); + + const filterActionsWrapper = document.body.querySelector('.mynah-detailed-list-filter-actions-wrapper'); + expect(filterActionsWrapper).toBeDefined(); + }); + + it('should update list items', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const updatedList: DetailedList = { + list: [ + { + groupName: 'Updated Group', + children: [ + { id: 'new-item', title: 'New Item', description: 'New description' } + ] + } + ] + }; + + detailedListWrapper.update(updatedList); + + const groupTitle = document.body.querySelector('.mynah-detailed-list-group-title'); + expect(groupTitle?.textContent).toContain('Updated Group'); + + const items = document.body.querySelectorAll('.mynah-detailed-list-item'); + expect(items.length).toBe(1); + }); + + it('should preserve scroll position when requested', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const groupsContainer = document.body.querySelector('.mynah-detailed-list-item-groups-wrapper') as HTMLElement; + Object.defineProperty(groupsContainer, 'scrollTop', { value: 100, writable: true }); + + const updatedList: DetailedList = { + list: [ + { + groupName: 'Updated Large Group', + children: Array.from({ length: 50 }, (_, i) => ({ + id: `updated-item-${i}`, + title: `Updated Item ${i}` + })) + } + ] + }; + + detailedListWrapper.update(updatedList, true); + + expect(requestAnimationFrame).toHaveBeenCalled(); + }); + + it('should reset scroll position when not preserving', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: largeDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const updatedList: DetailedList = { + list: [ + { + groupName: 'New Group', + children: [ { id: 'new-item', title: 'New Item' } ] + } + ] + }; + + detailedListWrapper.update(updatedList, false); + + const groupsContainer = document.body.querySelector('.mynah-detailed-list-item-groups-wrapper') as HTMLElement; + expect(groupsContainer).toBeDefined(); + }); + + it('should update selectable property', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + const updatedList: DetailedList = { + selectable: 'clickable', + list: basicDetailedList.list + }; + + detailedListWrapper.update(updatedList); + + expect(detailedListWrapper.render).toBeDefined(); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty list', () => { + const emptyList: DetailedList = { list: [] }; + detailedListWrapper = new DetailedListWrapper({ detailedList: emptyList }); + document.body.appendChild(detailedListWrapper.render); + + const items = document.body.querySelectorAll('.mynah-detailed-list-item'); + expect(items.length).toBe(0); + }); + + it('should handle list with empty groups', () => { + const listWithEmptyGroups: DetailedList = { + list: [ + { groupName: 'Empty Group', children: [] }, + { groupName: 'Another Empty Group', children: undefined } + ] + }; + + detailedListWrapper = new DetailedListWrapper({ detailedList: listWithEmptyGroups }); + document.body.appendChild(detailedListWrapper.render); + + const groups = document.body.querySelectorAll('.mynah-detailed-list-group'); + expect(groups.length).toBe(2); + + const items = document.body.querySelectorAll('.mynah-detailed-list-item'); + expect(items.length).toBe(0); + }); + + it('should handle null/undefined list', () => { + const nullList: DetailedList = { list: undefined }; + detailedListWrapper = new DetailedListWrapper({ detailedList: nullList }); + document.body.appendChild(detailedListWrapper.render); + + const groupsWrapper = document.body.querySelector('.mynah-detailed-list-item-groups-wrapper'); + expect(groupsWrapper?.textContent?.trim()).toBe(''); + }); + + it('should handle update with partial data', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: detailedListWithHeader }); + document.body.appendChild(detailedListWrapper.render); + + // Update with only some properties + detailedListWrapper.update({}); + + // Should not throw error + expect(detailedListWrapper.render).toBeDefined(); + }); + + it('should handle navigation with no selectable elements', () => { + const emptyList: DetailedList = { list: [] }; + detailedListWrapper = new DetailedListWrapper({ detailedList: emptyList }); + + // Should not throw error + detailedListWrapper.changeTarget('up'); + detailedListWrapper.changeTarget('down'); + expect(detailedListWrapper.render).toBeDefined(); + }); + + it('should handle getTargetElement with negative index', () => { + detailedListWrapper = new DetailedListWrapper({ detailedList: basicDetailedList }); + document.body.appendChild(detailedListWrapper.render); + + // Manually set a negative index to test Math.max + (detailedListWrapper as any).activeTargetElementIndex = -1; + + const targetElement = detailedListWrapper.getTargetElement(); + expect(targetElement).toBeDefined(); // Should use Math.max(index, 0) + }); + + it('should handle filter options with empty array', () => { + const listWithEmptyFilters: DetailedList = { + filterOptions: [], + list: basicDetailedList.list + }; + + detailedListWrapper = new DetailedListWrapper({ + detailedList: listWithEmptyFilters, + onFilterValueChange: mockOnFilterValueChange + }); + document.body.appendChild(detailedListWrapper.render); + + const filtersWrapper = document.body.querySelector('.mynah-detailed-list-filters-wrapper'); + expect(filtersWrapper?.textContent?.trim()).toBe(''); + }); + + it('should handle filter options with null', () => { + const listWithNullFilters: DetailedList = { + filterOptions: null, + list: basicDetailedList.list + }; + + detailedListWrapper = new DetailedListWrapper({ + detailedList: listWithNullFilters, + onFilterValueChange: mockOnFilterValueChange + }); + document.body.appendChild(detailedListWrapper.render); + + const filtersWrapper = document.body.querySelector('.mynah-detailed-list-filters-wrapper'); + expect(filtersWrapper?.textContent?.trim()).toBe(''); + }); + }); + + describe('Text Direction', () => { + it('should pass text direction to items', () => { + const listWithTextDirection: DetailedList = { + ...basicDetailedList, + textDirection: 'column' + }; + + detailedListWrapper = new DetailedListWrapper({ + detailedList: listWithTextDirection, + descriptionTextDirection: 'rtl' + }); + document.body.appendChild(detailedListWrapper.render); + + const textContainer = document.body.querySelector('.mynah-detailed-list-item-text'); + expect(textContainer?.classList.contains('mynah-detailed-list-item-text-direction-column')).toBe(true); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-comment.spec.ts b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-comment.spec.ts new file mode 100644 index 00000000..088c5741 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-comment.spec.ts @@ -0,0 +1,216 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FeedbackFormComment } from '../../../components/feedback-form/feedback-form-comment'; +import { Config } from '../../../helper/config'; +import testIds from '../../../helper/test-ids'; + +// Mock Config +jest.mock('../../../helper/config'); + +describe('FeedbackFormComment Component', () => { + let feedbackFormComment: FeedbackFormComment; + let mockOnChange: jest.Mock; + let mockConfig: jest.Mocked; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + + // Setup Config mock to enable test mode + mockConfig = { + config: { + test: true + } + } as any; + (Config.getInstance as jest.Mock).mockReturnValue(mockConfig); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + }); + + describe('Constructor', () => { + it('should create feedback form comment with default props', () => { + feedbackFormComment = new FeedbackFormComment({}); + + expect(feedbackFormComment.render).toBeDefined(); + expect(feedbackFormComment.render.tagName).toBe('TEXTAREA'); + expect(feedbackFormComment.render.classList.contains('mynah-feedback-form-comment')).toBe(true); + expect(feedbackFormComment.render.getAttribute('data-testid')).toBe(testIds.feedbackForm.comment); + }); + + it('should create feedback form comment with initial comment', () => { + const initComment = 'Initial feedback comment'; + feedbackFormComment = new FeedbackFormComment({ initComment }); + + expect(feedbackFormComment.render.value).toBe(initComment); + expect(feedbackFormComment.getComment()).toBe(initComment); + }); + + it('should create feedback form comment with onChange callback', () => { + feedbackFormComment = new FeedbackFormComment({ onChange: mockOnChange }); + + expect(feedbackFormComment.render).toBeDefined(); + expect(mockOnChange).not.toHaveBeenCalled(); + }); + + it('should create feedback form comment with both initial comment and onChange', () => { + const initComment = 'Test comment'; + feedbackFormComment = new FeedbackFormComment({ + initComment, + onChange: mockOnChange + }); + + expect(feedbackFormComment.render.value).toBe(initComment); + expect(feedbackFormComment.getComment()).toBe(initComment); + expect(mockOnChange).not.toHaveBeenCalled(); + }); + }); + + describe('Event Handling', () => { + it('should call onChange when keyup event is triggered', () => { + feedbackFormComment = new FeedbackFormComment({ onChange: mockOnChange }); + document.body.appendChild(feedbackFormComment.render); + + const testValue = 'New comment text'; + feedbackFormComment.render.value = testValue; + + const keyupEvent = new KeyboardEvent('keyup', { key: 'a' }); + feedbackFormComment.render.dispatchEvent(keyupEvent); + + expect(mockOnChange).toHaveBeenCalledWith(testValue); + expect(mockOnChange).toHaveBeenCalledTimes(1); + }); + + it('should call onChange multiple times for multiple keyup events', () => { + feedbackFormComment = new FeedbackFormComment({ onChange: mockOnChange }); + document.body.appendChild(feedbackFormComment.render); + + // First keyup + feedbackFormComment.render.value = 'First'; + feedbackFormComment.render.dispatchEvent(new KeyboardEvent('keyup', { key: 'a' })); + + // Second keyup + feedbackFormComment.render.value = 'First Second'; + feedbackFormComment.render.dispatchEvent(new KeyboardEvent('keyup', { key: 'b' })); + + expect(mockOnChange).toHaveBeenCalledTimes(2); + expect(mockOnChange).toHaveBeenNthCalledWith(1, 'First'); + expect(mockOnChange).toHaveBeenNthCalledWith(2, 'First Second'); + }); + + it('should not throw error when onChange is not provided and keyup is triggered', () => { + feedbackFormComment = new FeedbackFormComment({}); + document.body.appendChild(feedbackFormComment.render); + + feedbackFormComment.render.value = 'Test value'; + + expect(() => { + feedbackFormComment.render.dispatchEvent(new KeyboardEvent('keyup', { key: 'a' })); + }).not.toThrow(); + }); + + it('should handle empty string values in onChange', () => { + feedbackFormComment = new FeedbackFormComment({ onChange: mockOnChange }); + document.body.appendChild(feedbackFormComment.render); + + feedbackFormComment.render.value = ''; + feedbackFormComment.render.dispatchEvent(new KeyboardEvent('keyup', { key: 'Backspace' })); + + expect(mockOnChange).toHaveBeenCalledWith(''); + }); + }); + + describe('Methods', () => { + beforeEach(() => { + feedbackFormComment = new FeedbackFormComment({ initComment: 'Initial comment' }); + }); + + it('should return current comment value with getComment', () => { + expect(feedbackFormComment.getComment()).toBe('Initial comment'); + + feedbackFormComment.render.value = 'Updated comment'; + expect(feedbackFormComment.getComment()).toBe('Updated comment'); + }); + + it('should clear comment value with clear method', () => { + expect(feedbackFormComment.getComment()).toBe('Initial comment'); + + feedbackFormComment.clear(); + + expect(feedbackFormComment.getComment()).toBe(''); + expect(feedbackFormComment.render.value).toBe(''); + }); + + it('should clear empty comment without issues', () => { + feedbackFormComment = new FeedbackFormComment({}); + + expect(feedbackFormComment.getComment()).toBe(''); + + feedbackFormComment.clear(); + + expect(feedbackFormComment.getComment()).toBe(''); + }); + }); + + describe('DOM Structure', () => { + it('should have correct HTML attributes', () => { + const initComment = 'Test comment'; + feedbackFormComment = new FeedbackFormComment({ initComment }); + + expect(feedbackFormComment.render.tagName).toBe('TEXTAREA'); + expect(feedbackFormComment.render.getAttribute('data-testid')).toBe(testIds.feedbackForm.comment); + expect(feedbackFormComment.render.classList.contains('mynah-feedback-form-comment')).toBe(true); + expect(feedbackFormComment.render.value).toBe(initComment); + }); + + it('should be focusable', () => { + feedbackFormComment = new FeedbackFormComment({}); + document.body.appendChild(feedbackFormComment.render); + + feedbackFormComment.render.focus(); + expect(document.activeElement).toBe(feedbackFormComment.render); + }); + + it('should handle special characters in initial comment', () => { + const specialComment = 'Comment with "quotes" and & symbols'; + feedbackFormComment = new FeedbackFormComment({ initComment: specialComment }); + + expect(feedbackFormComment.getComment()).toBe(specialComment); + expect(feedbackFormComment.render.value).toBe(specialComment); + }); + }); + + describe('Integration with onChange', () => { + it('should trigger onChange with correct value after programmatic value change', () => { + feedbackFormComment = new FeedbackFormComment({ onChange: mockOnChange }); + document.body.appendChild(feedbackFormComment.render); + + // Simulate user typing + const newValue = 'User typed this'; + feedbackFormComment.render.value = newValue; + feedbackFormComment.render.dispatchEvent(new KeyboardEvent('keyup')); + + expect(mockOnChange).toHaveBeenCalledWith(newValue); + }); + + it('should work with onChange callback that modifies external state', () => { + let externalState = ''; + const stateUpdater = (value: string): void => { + externalState = value.toUpperCase(); + }; + + feedbackFormComment = new FeedbackFormComment({ onChange: stateUpdater }); + document.body.appendChild(feedbackFormComment.render); + + feedbackFormComment.render.value = 'test input'; + feedbackFormComment.render.dispatchEvent(new KeyboardEvent('keyup')); + + expect(externalState).toBe('TEST INPUT'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-coverage-simple.spec.ts b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-coverage-simple.spec.ts new file mode 100644 index 00000000..b291ec47 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-coverage-simple.spec.ts @@ -0,0 +1,305 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FeedbackForm } from '../../../components/feedback-form/feedback-form'; +import { MynahEventNames, FeedbackPayload, ChatItemFormItem } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { Config } from '../../../helper/config'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import testIds from '../../../helper/test-ids'; + +// Mock dependencies +jest.mock('../../../helper/config'); +jest.mock('../../../helper/tabs-store'); +jest.mock('../../../helper/events'); + +describe('FeedbackForm Simple Coverage Tests', () => { + let feedbackForm: FeedbackForm; + let mockConfig: jest.Mocked; + let mockGlobalEvents: jest.Mocked; + let mockTabsStore: jest.Mocked; + + const mockFeedbackOptions = [ + { value: 'helpful', label: 'Helpful' }, + { value: 'not-helpful', label: 'Not Helpful' }, + { value: 'inaccurate', label: 'Inaccurate' } + ]; + + const mockTexts = { + feedbackFormOptionsLabel: 'How was this response?', + feedbackFormCommentLabel: 'Tell us more (optional)', + feedbackFormTitle: 'Feedback', + feedbackFormDescription: 'Help us improve', + submit: 'Submit', + cancel: 'Cancel' + }; + + beforeEach(() => { + document.body.innerHTML = ''; + + // Setup Config mock + mockConfig = { + config: { + feedbackOptions: mockFeedbackOptions, + texts: mockTexts, + test: true, + componentClasses: {} + } + } as any; + (Config.getInstance as jest.Mock).mockReturnValue(mockConfig); + + // Setup GlobalEvents mock + mockGlobalEvents = { + addListener: jest.fn(), + dispatch: jest.fn() + } as any; + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue(mockGlobalEvents); + + // Setup TabsStore mock + mockTabsStore = { + getTabDataStore: jest.fn().mockReturnValue({ tabId: 'test-tab' }) + } as any; + (MynahUITabsStore.getInstance as jest.Mock).mockReturnValue(mockTabsStore); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + }); + + describe('InitPayload Comment Optional Chaining Coverage', () => { + it('should handle undefined comment in initPayload', () => { + const initPayload: FeedbackPayload = { + messageId: 'msg-123', + tabId: 'tab-456', + selectedOption: 'helpful' + // comment is undefined - this tests the optional chaining on line 51 + }; + + expect(() => { + feedbackForm = new FeedbackForm({ initPayload }); + }).not.toThrow(); + + expect(feedbackForm).toBeDefined(); + }); + + it('should handle null comment in initPayload', () => { + const initPayload: any = { + messageId: 'msg-123', + tabId: 'tab-456', + selectedOption: 'helpful', + comment: null // This tests the optional chaining + }; + + expect(() => { + feedbackForm = new FeedbackForm({ initPayload }); + }).not.toThrow(); + + expect(feedbackForm).toBeDefined(); + }); + }); + + describe('Custom Form Data Optional Chaining Coverage', () => { + it('should handle custom form data with undefined title and description', () => { + feedbackForm = new FeedbackForm(); + + const customFormData = { + formItems: [ { id: 'input1', type: 'textinput' } ] as ChatItemFormItem[] + // title and description are undefined - tests optional chaining on lines 118-125 + }; + + const eventData = { + tabId: 'tab-456', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + + expect(() => { + callback(eventData); + }).not.toThrow(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: undefined, + description: undefined + }) + ); + }); + + it('should handle custom form data with null values', () => { + feedbackForm = new FeedbackForm(); + + const customFormData: any = { + title: null, + description: null, + formItems: [ { id: 'input1', type: 'textinput' } ] as ChatItemFormItem[] + }; + + const eventData = { + tabId: 'tab-456', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + + expect(() => { + callback(eventData); + }).not.toThrow(); + }); + }); + + describe('Comment Change Coverage', () => { + it('should handle comment changes through the feedback comment component', () => { + feedbackForm = new FeedbackForm(); + + // Get the comment component from the form items + const formItems = feedbackForm.defaultFeedbackFormItems; + const commentTextarea = formItems.find(item => + item.getAttribute != null && item.getAttribute('data-testid') === testIds.feedbackForm.comment + ) as unknown as HTMLTextAreaElement; + + expect(commentTextarea).toBeDefined(); + + // Simulate typing in the comment field + document.body.appendChild(commentTextarea); + commentTextarea.value = 'Test comment'; + commentTextarea.dispatchEvent(new KeyboardEvent('keyup')); + + // Submit the form to verify the comment was captured + const buttonsContainer = feedbackForm.defaultFeedbackFormItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + submitButton.click(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.objectContaining({ + comment: 'Test comment' + }) + ); + }); + }); + + describe('Edge Cases for Better Coverage', () => { + it('should handle empty custom form data', () => { + feedbackForm = new FeedbackForm(); + + const eventData = { + tabId: 'tab-456', + customFormData: {} + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + + expect(() => { + callback(eventData); + }).not.toThrow(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + children: [] + }) + ); + }); + + it('should handle form with both messageId and customFormData (messageId takes precedence)', () => { + feedbackForm = new FeedbackForm(); + + const eventData = { + messageId: 'msg-123', + tabId: 'tab-456', + customFormData: { + title: 'Custom Title', + description: 'Custom Description' + } + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + // When messageId is present, it should use the default feedback form + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: mockTexts.feedbackFormTitle, + description: mockTexts.feedbackFormDescription, + children: feedbackForm.defaultFeedbackFormItems + }) + ); + }); + + it('should handle multiple form submissions', () => { + feedbackForm = new FeedbackForm(); + + const buttonsContainer = feedbackForm.defaultFeedbackFormItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + // Submit multiple times + submitButton.click(); + submitButton.click(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledTimes(4); // 2 FEEDBACK_SET + 2 CLOSE_SHEET + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.any(Object) + ); + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + }); + + it('should handle close method multiple times', () => { + feedbackForm = new FeedbackForm(); + + // Call close multiple times + feedbackForm.close(); + feedbackForm.close(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + }); + }); + + describe('Constructor Variations Coverage', () => { + it('should create feedback form without props', () => { + feedbackForm = new FeedbackForm(); + expect(feedbackForm).toBeDefined(); + expect(feedbackForm.defaultFeedbackFormItems).toHaveLength(4); + }); + + it('should create feedback form with empty props', () => { + feedbackForm = new FeedbackForm({}); + expect(feedbackForm).toBeDefined(); + expect(feedbackForm.defaultFeedbackFormItems).toHaveLength(4); + }); + + it('should create feedback form with partial initPayload', () => { + const initPayload: Partial = { + messageId: 'msg-123' + // Other fields are undefined + }; + + feedbackForm = new FeedbackForm({ initPayload: initPayload as FeedbackPayload }); + expect(feedbackForm).toBeDefined(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-integration.spec.ts b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-integration.spec.ts new file mode 100644 index 00000000..4823ae25 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form-integration.spec.ts @@ -0,0 +1,416 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FeedbackForm } from '../../../components/feedback-form/feedback-form'; +import { FeedbackFormComment } from '../../../components/feedback-form/feedback-form-comment'; +import { MynahEventNames, FeedbackPayload } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { Config } from '../../../helper/config'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import testIds from '../../../helper/test-ids'; + +// Mock dependencies +jest.mock('../../../helper/config'); +jest.mock('../../../helper/tabs-store'); +jest.mock('../../../helper/events'); + +describe('FeedbackForm Integration Tests', () => { + let feedbackForm: FeedbackForm; + let mockConfig: jest.Mocked; + let mockGlobalEvents: jest.Mocked; + let mockTabsStore: jest.Mocked; + + const mockFeedbackOptions = [ + { value: 'helpful', label: 'Helpful' }, + { value: 'not-helpful', label: 'Not Helpful' }, + { value: 'inaccurate', label: 'Inaccurate' } + ]; + + const mockTexts = { + feedbackFormOptionsLabel: 'How was this response?', + feedbackFormCommentLabel: 'Tell us more (optional)', + feedbackFormTitle: 'Feedback', + feedbackFormDescription: 'Help us improve', + submit: 'Submit', + cancel: 'Cancel' + }; + + beforeEach(() => { + document.body.innerHTML = ''; + + // Setup Config mock + mockConfig = { + config: { + feedbackOptions: mockFeedbackOptions, + texts: mockTexts, + test: true, // Enable test mode for testId attributes + componentClasses: {} // Add componentClasses to prevent Select component errors + } + } as any; + (Config.getInstance as jest.Mock).mockReturnValue(mockConfig); + + // Setup GlobalEvents mock + mockGlobalEvents = { + addListener: jest.fn(), + dispatch: jest.fn() + } as any; + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue(mockGlobalEvents); + + // Setup TabsStore mock + mockTabsStore = { + getTabDataStore: jest.fn().mockReturnValue({ tabId: 'test-tab' }) + } as any; + (MynahUITabsStore.getInstance as jest.Mock).mockReturnValue(mockTabsStore); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + }); + + describe('Complete Feedback Flow', () => { + it('should handle complete feedback submission flow', () => { + // Create feedback form + feedbackForm = new FeedbackForm(); + + // Simulate showing feedback form + const showFeedbackData = { + messageId: 'msg-123', + tabId: 'tab-456' + }; + + const showFeedbackCallback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + showFeedbackCallback(showFeedbackData); + + // Verify form was opened + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: mockTexts.feedbackFormTitle, + description: mockTexts.feedbackFormDescription + }) + ); + + // Get the form items + const formItems = feedbackForm.defaultFeedbackFormItems; + + // Find comment textarea + const commentTextarea = formItems.find(item => + item.getAttribute != null && item.getAttribute('data-testid') === testIds.feedbackForm.comment + ) as unknown as HTMLTextAreaElement; + + expect(commentTextarea).toBeDefined(); + + // Simulate user typing in comment + document.body.appendChild(commentTextarea); + commentTextarea.value = 'This is my feedback comment'; + commentTextarea.dispatchEvent(new KeyboardEvent('keyup')); + + // Find and click submit button + const buttonsContainer = formItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + expect(buttonsContainer).toBeDefined(); + + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + expect(submitButton).toBeDefined(); + + submitButton.click(); + + // Verify feedback was submitted + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.objectContaining({ + messageId: 'msg-123', + tabId: 'tab-456', + selectedOption: 'helpful', // Default first option + comment: 'This is my feedback comment' + }) + ); + + // Verify form was closed + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + }); + + it('should handle feedback cancellation flow', () => { + feedbackForm = new FeedbackForm(); + + // Simulate showing feedback form + const showFeedbackData = { + messageId: 'msg-123', + tabId: 'tab-456' + }; + + const showFeedbackCallback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + showFeedbackCallback(showFeedbackData); + + // Get form items and add comment + const formItems = feedbackForm.defaultFeedbackFormItems; + const commentTextarea = formItems.find(item => + item.getAttribute != null && item.getAttribute('data-testid') === testIds.feedbackForm.comment + ) as unknown as HTMLTextAreaElement; + + document.body.appendChild(commentTextarea); + commentTextarea.value = 'This comment should be cleared'; + commentTextarea.dispatchEvent(new KeyboardEvent('keyup')); + + // Find and click cancel button + const buttonsContainer = formItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + expect(buttonsContainer).toBeDefined(); + + document.body.appendChild(buttonsContainer as HTMLElement); + const cancelButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.cancelButton}"]`) as HTMLButtonElement; + + expect(cancelButton).toBeDefined(); + + cancelButton.click(); + + // Verify form was closed without submitting feedback + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + + // Verify FEEDBACK_SET was not called + expect(mockGlobalEvents.dispatch).not.toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.any(Object) + ); + }); + + it('should handle feedback form with initial payload', () => { + const initPayload: FeedbackPayload = { + messageId: 'existing-msg', + tabId: 'existing-tab', + selectedOption: 'not-helpful', + comment: 'Initial comment' + }; + + feedbackForm = new FeedbackForm({ initPayload }); + + // Get comment component and verify initial value + const formItems = feedbackForm.defaultFeedbackFormItems; + const commentTextarea = formItems.find(item => + item.getAttribute != null && item.getAttribute('data-testid') === testIds.feedbackForm.comment + ) as unknown as HTMLTextAreaElement; + + expect(commentTextarea.value).toBe('Initial comment'); + + // Submit the form + const buttonsContainer = formItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + submitButton.click(); + + // Verify the initial payload values are used + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.objectContaining({ + selectedOption: 'not-helpful', + comment: 'Initial comment' + }) + ); + }); + }); + + describe('Comment Component Integration', () => { + it('should properly integrate comment component with feedback form', () => { + feedbackForm = new FeedbackForm(); + + const formItems = feedbackForm.defaultFeedbackFormItems; + const commentTextarea = formItems.find(item => + item.getAttribute != null && item.getAttribute('data-testid') === testIds.feedbackForm.comment + ) as unknown as HTMLTextAreaElement; + + // Verify comment component is properly integrated + expect(commentTextarea).toBeDefined(); + expect(commentTextarea.tagName).toBe('TEXTAREA'); + expect(commentTextarea.classList.contains('mynah-feedback-form-comment')).toBe(true); + + // Test comment functionality + document.body.appendChild(commentTextarea); + commentTextarea.value = 'Test comment'; + + // Create a FeedbackFormComment instance to test methods + const commentComponent = new FeedbackFormComment({ + initComment: 'Test comment' + }); + + expect(commentComponent.getComment()).toBe('Test comment'); + + commentComponent.clear(); + expect(commentComponent.getComment()).toBe(''); + }); + + it('should handle comment changes and form submission together', () => { + let capturedComment = ''; + + // Create comment component with onChange + const commentComponent = new FeedbackFormComment({ + onChange: (comment) => { + capturedComment = comment; + } + }); + + document.body.appendChild(commentComponent.render); + + // Simulate user typing + commentComponent.render.value = 'User feedback'; + commentComponent.render.dispatchEvent(new KeyboardEvent('keyup')); + + expect(capturedComment).toBe('User feedback'); + + // Test clearing + commentComponent.clear(); + expect(commentComponent.getComment()).toBe(''); + }); + }); + + describe('Form State Management', () => { + it('should maintain form state throughout interaction', () => { + feedbackForm = new FeedbackForm(); + + // Show feedback form + const showFeedbackData = { + messageId: 'msg-123', + tabId: 'tab-456' + }; + + const showFeedbackCallback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + showFeedbackCallback(showFeedbackData); + + // Modify form state + const formItems = feedbackForm.defaultFeedbackFormItems; + const commentTextarea = formItems.find(item => + item.getAttribute != null && item.getAttribute('data-testid') === testIds.feedbackForm.comment + ) as unknown as HTMLTextAreaElement; + + document.body.appendChild(commentTextarea); + commentTextarea.value = 'Modified comment'; + commentTextarea.dispatchEvent(new KeyboardEvent('keyup')); + + // Submit form + const buttonsContainer = formItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + submitButton.click(); + + // Verify state was maintained and submitted + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.objectContaining({ + messageId: 'msg-123', + tabId: 'tab-456', + comment: 'Modified comment' + }) + ); + }); + + it('should reset form state after close', () => { + const initPayload: FeedbackPayload = { + messageId: 'msg-123', + tabId: 'tab-456', + selectedOption: 'not-helpful', + comment: 'Some comment' + }; + + feedbackForm = new FeedbackForm({ initPayload }); + + // Close the form + feedbackForm.close(); + + // Verify close event was dispatched + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + + // Verify form items are reset + const formItems = feedbackForm.defaultFeedbackFormItems; + const commentTextarea = formItems.find(item => + item.getAttribute != null && item.getAttribute('data-testid') === testIds.feedbackForm.comment + ) as unknown as HTMLTextAreaElement; + + expect(commentTextarea.value).toBe(''); + }); + }); + + describe('Error Handling', () => { + it('should handle missing tab data gracefully', () => { + // Override the mock for this specific test + mockTabsStore.getTabDataStore.mockReturnValueOnce(undefined as any); + + feedbackForm = new FeedbackForm(); + + const customFormData = { + title: 'Custom Form', + formItems: [ { id: 'input1', type: 'textinput' } ] as any[] + }; + + const eventData = { + tabId: 'invalid-tab', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + + // Should not throw error + expect(() => { + callback(eventData); + }).not.toThrow(); + + // Should open sheet with empty children + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + children: [] + }) + ); + }); + + it('should handle form submission without comment gracefully', () => { + feedbackForm = new FeedbackForm(); + + const formItems = feedbackForm.defaultFeedbackFormItems; + const buttonsContainer = formItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + // Submit without adding comment + expect(() => { + submitButton.click(); + }).not.toThrow(); + + // Should submit with empty comment + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.objectContaining({ + comment: '' + }) + ); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form.spec.ts b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form.spec.ts new file mode 100644 index 00000000..74352952 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/feedback-form/feedback-form.spec.ts @@ -0,0 +1,465 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FeedbackForm } from '../../../components/feedback-form/feedback-form'; +import { MynahEventNames, FeedbackPayload, ChatItemButton, ChatItemFormItem } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { Config } from '../../../helper/config'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import testIds from '../../../helper/test-ids'; + +// Mock dependencies +jest.mock('../../../helper/config'); +jest.mock('../../../helper/tabs-store'); +jest.mock('../../../helper/events'); + +describe('FeedbackForm Component', () => { + let feedbackForm: FeedbackForm; + let mockConfig: jest.Mocked; + let mockGlobalEvents: jest.Mocked; + let mockTabsStore: jest.Mocked; + + const mockFeedbackOptions = [ + { value: 'helpful', label: 'Helpful' }, + { value: 'not-helpful', label: 'Not Helpful' }, + { value: 'inaccurate', label: 'Inaccurate' } + ]; + + const mockTexts = { + feedbackFormOptionsLabel: 'How was this response?', + feedbackFormCommentLabel: 'Tell us more (optional)', + feedbackFormTitle: 'Feedback', + feedbackFormDescription: 'Help us improve', + submit: 'Submit', + cancel: 'Cancel' + }; + + beforeEach(() => { + document.body.innerHTML = ''; + + // Setup Config mock + mockConfig = { + config: { + feedbackOptions: mockFeedbackOptions, + texts: mockTexts, + test: true, // Enable test mode for testId attributes + componentClasses: {} // Add componentClasses to prevent Select component errors + } + } as any; + (Config.getInstance as jest.Mock).mockReturnValue(mockConfig); + + // Setup GlobalEvents mock + mockGlobalEvents = { + addListener: jest.fn(), + dispatch: jest.fn() + } as any; + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue(mockGlobalEvents); + + // Setup TabsStore mock + mockTabsStore = { + getTabDataStore: jest.fn().mockReturnValue({ tabId: 'test-tab' }) + } as any; + (MynahUITabsStore.getInstance as jest.Mock).mockReturnValue(mockTabsStore); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.clearAllMocks(); + }); + + describe('Constructor', () => { + it('should create feedback form with default configuration', () => { + feedbackForm = new FeedbackForm(); + + expect(feedbackForm).toBeDefined(); + expect(feedbackForm.defaultFeedbackFormItems).toBeDefined(); + expect(feedbackForm.defaultFeedbackFormItems).toHaveLength(4); + }); + + it('should create feedback form with initial payload', () => { + const initPayload: FeedbackPayload = { + messageId: 'msg-123', + tabId: 'tab-456', + selectedOption: 'helpful', + comment: 'Great response!' + }; + + feedbackForm = new FeedbackForm({ initPayload }); + + expect(feedbackForm).toBeDefined(); + }); + + it('should register event listener for SHOW_FEEDBACK_FORM', () => { + feedbackForm = new FeedbackForm(); + + expect(mockGlobalEvents.addListener).toHaveBeenCalledWith( + MynahEventNames.SHOW_FEEDBACK_FORM, + expect.any(Function) + ); + }); + + it('should initialize with first feedback option as default', () => { + feedbackForm = new FeedbackForm(); + + // The constructor should set the default selected option to the first option + expect(mockConfig.config.feedbackOptions[0].value).toBe('helpful'); + }); + }); + + describe('Event Handling', () => { + beforeEach(() => { + feedbackForm = new FeedbackForm(); + }); + + it('should handle SHOW_FEEDBACK_FORM event with messageId', () => { + const eventData = { + messageId: 'msg-123', + tabId: 'tab-456' + }; + + // Get the registered callback + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: mockTexts.feedbackFormTitle, + description: mockTexts.feedbackFormDescription, + children: expect.any(Array) + }) + ); + }); + + it('should handle SHOW_FEEDBACK_FORM event with custom form data', () => { + const customFormData = { + title: 'Custom Title', + description: 'Custom Description', + buttons: [ { id: 'btn1', text: 'Custom Button' } ] as ChatItemButton[], + formItems: [ { id: 'item1', type: 'textinput' } ] as ChatItemFormItem[] + }; + + const eventData = { + tabId: 'tab-456', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: 'Custom Title', + description: 'Custom Description' + }) + ); + }); + + it('should handle SHOW_FEEDBACK_FORM event without messageId or customFormData', () => { + const eventData = { + tabId: 'tab-456' + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: undefined, + description: undefined, + children: [] + }) + ); + }); + }); + + describe('Form Submission', () => { + beforeEach(() => { + feedbackForm = new FeedbackForm(); + }); + + it('should dispatch FEEDBACK_SET event when submit button is clicked', () => { + // Get the buttons container + const buttonsContainer = feedbackForm.defaultFeedbackFormItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + expect(buttonsContainer).toBeDefined(); + + // Find submit button within the container + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + expect(submitButton).toBeDefined(); + + // Simulate button click + submitButton.click(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.FEEDBACK_SET, + expect.objectContaining({ + selectedOption: 'helpful', // Default first option + messageId: '', + tabId: '', + comment: '' + }) + ); + }); + + it('should close form after submission', () => { + const buttonsContainer = feedbackForm.defaultFeedbackFormItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + document.body.appendChild(buttonsContainer as HTMLElement); + const submitButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`) as HTMLButtonElement; + + submitButton.click(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + }); + }); + + describe('Form Cancellation', () => { + beforeEach(() => { + feedbackForm = new FeedbackForm(); + }); + + it('should close form when cancel button is clicked', () => { + const buttonsContainer = feedbackForm.defaultFeedbackFormItems.find(item => + item.classList?.contains('mynah-feedback-form-buttons-container') + ); + + expect(buttonsContainer).toBeDefined(); + + document.body.appendChild(buttonsContainer as HTMLElement); + const cancelButton = buttonsContainer?.querySelector(`[data-testid="${testIds.feedbackForm.cancelButton}"]`) as HTMLButtonElement; + + expect(cancelButton).toBeDefined(); + + cancelButton.click(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + }); + }); + + describe('Close Method', () => { + beforeEach(() => { + feedbackForm = new FeedbackForm(); + }); + + it('should reset form state when closed', () => { + feedbackForm.close(); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + }); + + it('should clear comment and reset selected option on close', () => { + // Set some initial state + const initPayload: FeedbackPayload = { + messageId: 'msg-123', + tabId: 'tab-456', + selectedOption: 'not-helpful', + comment: 'Some comment' + }; + + feedbackForm = new FeedbackForm({ initPayload }); + feedbackForm.close(); + + // After close, the form should be reset to defaults + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.CLOSE_SHEET, + {} + ); + }); + }); + + describe('Custom Form Handling', () => { + beforeEach(() => { + feedbackForm = new FeedbackForm(); + }); + + it('should handle custom form with form items', () => { + const customFormData = { + title: 'Custom Form', + formItems: [ + { id: 'input1', type: 'textinput', title: 'Name' } + ] as ChatItemFormItem[] + }; + + const eventData = { + tabId: 'tab-456', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: 'Custom Form' + }) + ); + }); + + it('should handle custom form with buttons', () => { + const customFormData = { + title: 'Custom Form', + buttons: [ + { id: 'submit', text: 'Submit Form' } + ] as ChatItemButton[] + }; + + const eventData = { + tabId: 'tab-456', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + tabId: 'tab-456', + title: 'Custom Form' + }) + ); + }); + + it('should return empty array when tab data store is undefined', () => { + // Override the mock for this specific test + mockTabsStore.getTabDataStore.mockReturnValueOnce(undefined as any); + + feedbackForm = new FeedbackForm(); + + const customFormData = { + title: 'Custom Form', + formItems: [ { id: 'input1', type: 'textinput' } ] as ChatItemFormItem[] + }; + + const eventData = { + tabId: 'invalid-tab', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.objectContaining({ + children: [] + }) + ); + }); + }); + + describe('Form Items Structure', () => { + beforeEach(() => { + feedbackForm = new FeedbackForm(); + }); + + it('should have correct structure for default feedback form items', () => { + const items = feedbackForm.defaultFeedbackFormItems; + + expect(items).toHaveLength(4); + + // First item should be the select wrapper + expect(items[0].classList.contains('mynah-form-input-wrapper')).toBe(true); + + // Second item should be the comment label + expect(items[1].tagName).toBe('SPAN'); + expect(items[1].textContent).toBe(mockTexts.feedbackFormCommentLabel); + + // Third item should be the comment textarea + expect(items[2].tagName).toBe('TEXTAREA'); + expect(items[2].getAttribute('data-testid')).toBe(testIds.feedbackForm.comment); + + // Fourth item should be the buttons container + expect(items[3].classList.contains('mynah-feedback-form-buttons-container')).toBe(true); + }); + + it('should have submit and cancel buttons in buttons container', () => { + const buttonsContainer = feedbackForm.defaultFeedbackFormItems[3]; + + expect(buttonsContainer.classList.contains('mynah-feedback-form-buttons-container')).toBe(true); + + // Append to DOM to query for buttons + document.body.appendChild(buttonsContainer); + + const cancelButton = buttonsContainer.querySelector(`[data-testid="${testIds.feedbackForm.cancelButton}"]`); + const submitButton = buttonsContainer.querySelector(`[data-testid="${testIds.feedbackForm.submitButton}"]`); + + expect(cancelButton).toBeDefined(); + expect(submitButton).toBeDefined(); + expect(cancelButton?.textContent).toBe(mockTexts.cancel); + expect(submitButton?.textContent).toBe(mockTexts.submit); + }); + }); + + describe('Event Dispatching', () => { + beforeEach(() => { + feedbackForm = new FeedbackForm(); + }); + + it('should dispatch custom form action click events', () => { + const customFormData = { + buttons: [ { id: 'custom-action', text: 'Custom Action' } ] as ChatItemButton[] + }; + + const eventData = { + tabId: 'tab-456', + customFormData + }; + + // Trigger the show feedback form event + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + // Verify that the form was opened + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.any(Object) + ); + }); + + it('should handle form change events', () => { + const customFormData = { + formItems: [ { id: 'input1', type: 'textinput' } ] as ChatItemFormItem[] + }; + + const eventData = { + tabId: 'tab-456', + customFormData + }; + + const callback = (mockGlobalEvents.addListener as jest.Mock).mock.calls[0][1]; + callback(eventData); + + // The form should be created and ready to handle form change events + expect(mockGlobalEvents.dispatch).toHaveBeenCalledWith( + MynahEventNames.OPEN_SHEET, + expect.any(Object) + ); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/feedback-form/index.spec.ts b/vendor/mynah-ui/src/__test__/components/feedback-form/index.spec.ts new file mode 100644 index 00000000..04a0a045 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/feedback-form/index.spec.ts @@ -0,0 +1,10 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// Export all feedback form tests for easier importing +export * from './feedback-form.spec'; +export * from './feedback-form-comment.spec'; +export * from './feedback-form-integration.spec'; +export * from './feedback-form-coverage-simple.spec'; diff --git a/vendor/mynah-ui/src/__test__/components/form-items/checkbox.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/checkbox.spec.ts new file mode 100644 index 00000000..d43064d9 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/checkbox.spec.ts @@ -0,0 +1,227 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Checkbox, CheckboxInternal, CheckboxProps } from '../../../components/form-items/checkbox'; +import { MynahIcons } from '../../../components/icon'; +import { DomBuilder } from '../../../helper/dom'; + +describe('Checkbox Component', () => { + let checkbox: CheckboxInternal; + let mockOnChange: jest.Mock; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('CheckboxInternal', () => { + it('should create checkbox with default props', () => { + checkbox = new CheckboxInternal({}); + + expect(checkbox.render).toBeDefined(); + expect(checkbox.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + expect(checkbox.getValue()).toBe('false'); + }); + + it('should create checkbox with initial value true', () => { + checkbox = new CheckboxInternal({ value: 'true' }); + + expect(checkbox.getValue()).toBe('true'); + }); + + it('should create checkbox with label', () => { + const label = 'Test Checkbox'; + checkbox = new CheckboxInternal({ label }); + + document.body.appendChild(checkbox.render); + const labelElement = document.body.querySelector('.mynah-form-input-radio-label'); + expect(labelElement?.textContent).toContain(label); + }); + + it('should create checkbox with title', () => { + const title = 'Checkbox Title'; + checkbox = new CheckboxInternal({ title }); + + document.body.appendChild(checkbox.render); + const titleElement = document.body.querySelector('.mynah-form-input-label'); + expect(titleElement?.textContent).toBe(title); + }); + + it('should create checkbox with custom icon', () => { + checkbox = new CheckboxInternal({ icon: MynahIcons.CANCEL }); + + document.body.appendChild(checkbox.render); + const iconElement = document.body.querySelector('.mynah-icon'); + expect(iconElement).toBeDefined(); + }); + + it('should create checkbox with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + checkbox = new CheckboxInternal({ classNames: customClasses }); + + expect(checkbox.render.querySelector('.custom-class-1')).toBeDefined(); + expect(checkbox.render.querySelector('.custom-class-2')).toBeDefined(); + }); + + it('should create checkbox with custom attributes', () => { + const attributes = { 'data-test': 'test-value', 'aria-label': 'test-checkbox' }; + checkbox = new CheckboxInternal({ attributes }); + + document.body.appendChild(checkbox.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.getAttribute('data-test')).toBe('test-value'); + expect(container?.getAttribute('aria-label')).toBe('test-checkbox'); + }); + + it('should create checkbox with test IDs', () => { + const wrapperTestId = 'wrapper-test-id'; + const optionTestId = 'option-test-id'; + checkbox = new CheckboxInternal({ wrapperTestId, optionTestId }); + + document.body.appendChild(checkbox.render); + const wrapper = document.body.querySelector(`[data-testid="${wrapperTestId}"]`); + const option = document.body.querySelector(`[data-testid="${optionTestId}"]`); + expect(wrapper).toBeDefined(); + expect(option).toBeDefined(); + }); + + it('should handle setValue method', () => { + checkbox = new CheckboxInternal({}); + + checkbox.setValue('true'); + expect(checkbox.getValue()).toBe('true'); + + checkbox.setValue('false'); + expect(checkbox.getValue()).toBe('false'); + }); + + it('should handle setEnabled method', () => { + checkbox = new CheckboxInternal({}); + document.body.appendChild(checkbox.render); + + const checkboxInput = document.body.querySelector('.as-checkbox') as HTMLInputElement; + const wrapper = document.body.querySelector('.mynah-form-input') as HTMLElement; + + // Test disabling + checkbox.setEnabled(false); + expect(checkboxInput.hasAttribute('disabled')).toBe(true); + expect(wrapper.hasAttribute('disabled')).toBe(true); + + // Test enabling + checkbox.setEnabled(true); + expect(checkboxInput.hasAttribute('disabled')).toBe(false); + expect(wrapper.hasAttribute('disabled')).toBe(false); + }); + + it('should trigger onChange when clicked', () => { + checkbox = new CheckboxInternal({ onChange: mockOnChange }); + document.body.appendChild(checkbox.render); + + const label = document.body.querySelector('.mynah-form-input-radio-label') as HTMLElement; + + // Click to check + label.click(); + expect(mockOnChange).toHaveBeenCalledWith('true'); + expect(checkbox.getValue()).toBe('true'); + + // Click to uncheck + label.click(); + expect(mockOnChange).toHaveBeenCalledWith('false'); + expect(checkbox.getValue()).toBe('false'); + }); + + it('should prevent event propagation on click', () => { + checkbox = new CheckboxInternal({ onChange: mockOnChange }); + document.body.appendChild(checkbox.render); + + const label = document.body.querySelector('.mynah-form-input-radio-label') as HTMLElement; + const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true }); + + const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault'); + const stopPropagationSpy = jest.spyOn(clickEvent, 'stopPropagation'); + + label.dispatchEvent(clickEvent); + + expect(preventDefaultSpy).toHaveBeenCalled(); + expect(stopPropagationSpy).toHaveBeenCalled(); + }); + + it('should handle description element', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + checkbox = new CheckboxInternal({ description: descriptionElement }); + document.body.appendChild(checkbox.render); + + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should handle optional property', () => { + checkbox = new CheckboxInternal({ optional: true }); + + // The optional property is passed but doesn't affect rendering in current implementation + expect(checkbox.render).toBeDefined(); + }); + }); + + describe('Checkbox Factory', () => { + it('should create CheckboxInternal by default', () => { + const checkboxFactory = new Checkbox({}); + expect(checkboxFactory).toBeInstanceOf(CheckboxInternal); + }); + + it('should have abstract methods', () => { + const checkboxFactory = new Checkbox({}); + expect(typeof checkboxFactory.setValue).toBe('function'); + expect(typeof checkboxFactory.getValue).toBe('function'); + expect(typeof checkboxFactory.setEnabled).toBe('function'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty props object', () => { + checkbox = new CheckboxInternal({}); + expect(checkbox.render).toBeDefined(); + expect(checkbox.getValue()).toBe('false'); + }); + + it('should handle null/undefined values gracefully', () => { + const props: CheckboxProps = { + title: undefined, + label: undefined, + description: undefined, + onChange: undefined + }; + + checkbox = new CheckboxInternal(props); + expect(checkbox.render).toBeDefined(); + }); + + it('should handle multiple rapid clicks', () => { + checkbox = new CheckboxInternal({ onChange: mockOnChange }); + document.body.appendChild(checkbox.render); + + const label = document.body.querySelector('.mynah-form-input-radio-label') as HTMLElement; + + // Rapid clicks + label.click(); + label.click(); + label.click(); + + expect(mockOnChange).toHaveBeenCalledTimes(3); + expect(mockOnChange).toHaveBeenNthCalledWith(1, 'true'); + expect(mockOnChange).toHaveBeenNthCalledWith(2, 'false'); + expect(mockOnChange).toHaveBeenNthCalledWith(3, 'true'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/form-item-list.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/form-item-list.spec.ts new file mode 100644 index 00000000..d8e2f337 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/form-item-list.spec.ts @@ -0,0 +1,391 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FormItemList, FormItemListInternal, FormItemListProps } from '../../../components/form-items/form-item-list'; +import { SingularFormItem, ListItemEntry } from '../../../static'; +import { DomBuilder } from '../../../helper/dom'; + +// Mock the generateUID helper +jest.mock('../../../helper/guid', () => ({ + generateUID: jest.fn(() => 'test-row-id') +})); + +// Mock the ChatItemFormItemsWrapper +jest.mock('../../../components/chat-item/chat-item-form-items', () => ({ + ChatItemFormItemsWrapper: jest.fn().mockImplementation((props) => ({ + render: document.createElement('div'), + getAllValues: jest.fn(() => ({ field1: 'value1', field2: 'value2' })), + enableAll: jest.fn(), + disableAll: jest.fn() + })) +})); + +describe('FormItemList Component', () => { + let formItemList: FormItemListInternal; + let mockOnChange: jest.Mock; + + const testFormItems: SingularFormItem[] = [ + { + id: 'field1', + type: 'textinput', + title: 'Field 1', + description: 'Description for field 1' + }, + { + id: 'field2', + type: 'textinput', + title: 'Field 2' + } + ]; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + jest.clearAllMocks(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('FormItemListInternal', () => { + it('should create form item list with default props', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + expect(formItemList.render).toBeDefined(); + expect(formItemList.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + }); + + it('should create form item list with label', () => { + const label = 'Test Form List'; + formItemList = new FormItemListInternal({ + items: testFormItems, + label + }); + + document.body.appendChild(formItemList.render); + const labelElement = document.body.querySelector('.mynah-form-input-label'); + expect(labelElement?.textContent).toBe(label); + }); + + it('should create form item list with description', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + formItemList = new FormItemListInternal({ + items: testFormItems, + description: descriptionElement + }); + + document.body.appendChild(formItemList.render); + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should create form item list with wrapper test ID', () => { + const wrapperTestId = 'wrapper-test-id'; + formItemList = new FormItemListInternal({ + items: testFormItems, + wrapperTestId + }); + + document.body.appendChild(formItemList.render); + const wrapper = document.body.querySelector(`[data-testid="${wrapperTestId}"]`); + expect(wrapper).toBeDefined(); + }); + + it('should create form item list with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + formItemList = new FormItemListInternal({ + items: testFormItems, + classNames: customClasses + }); + + // Note: classNames are not currently used in the implementation + expect(formItemList.render).toBeDefined(); + }); + + it('should create form item list with custom attributes', () => { + const attributes = { 'data-test': 'test-value' }; + formItemList = new FormItemListInternal({ + items: testFormItems, + attributes + }); + + // Note: attributes are not currently used in the implementation + expect(formItemList.render).toBeDefined(); + }); + + it('should initialize with one empty row by default', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + document.body.appendChild(formItemList.render); + const rows = document.body.querySelectorAll('.mynah-form-item-list-row'); + + // Should have header row + 1 data row + expect(rows.length).toBeGreaterThanOrEqual(1); + }); + + it('should initialize with provided values', () => { + const initialValues: ListItemEntry[] = [ + { value: { field1: 'value1', field2: 'value2' } }, + { value: { field1: 'value3', field2: 'value4' } } + ]; + + formItemList = new FormItemListInternal({ + items: testFormItems, + value: initialValues + }); + + document.body.appendChild(formItemList.render); + const rows = document.body.querySelectorAll('.mynah-form-item-list-row'); + + // Should have header row + 2 data rows + expect(rows.length).toBeGreaterThanOrEqual(2); + }); + + it('should have add button', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + document.body.appendChild(formItemList.render); + const addButton = document.body.querySelector('.mynah-form-item-list-add-button'); + expect(addButton).toBeDefined(); + }); + + it('should have remove buttons for each row', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + document.body.appendChild(formItemList.render); + const removeButtons = document.body.querySelectorAll('.mynah-form-item-list-row-remove-button'); + expect(removeButtons.length).toBeGreaterThanOrEqual(1); + }); + + it('should create header row with item titles and descriptions', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + document.body.appendChild(formItemList.render); + const headers = document.body.querySelectorAll('.mynah-form-item-list-row-header'); + + // Should have headers for items with title or description + expect(headers.length).toBeGreaterThanOrEqual(1); + }); + + it('should handle onChange callback', () => { + formItemList = new FormItemListInternal({ + items: testFormItems, + onChange: mockOnChange + }); + + // onChange should be called during initialization + expect(mockOnChange).toHaveBeenCalled(); + }); + + it('should handle getValue method', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + const values = formItemList.getValue(); + expect(Array.isArray(values)).toBe(true); + expect(values.length).toBeGreaterThanOrEqual(1); + }); + + it('should handle setValue method', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + const newValues: ListItemEntry[] = [ + { value: { field1: 'new1', field2: 'new2' } } + ]; + + formItemList.setValue(newValues); + + // Should update the form with new values + expect(formItemList.render).toBeDefined(); + }); + + it('should handle setValue with empty array', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + formItemList.setValue([]); + + // Should create one empty row + const values = formItemList.getValue(); + expect(values.length).toBeGreaterThanOrEqual(1); + }); + + it('should handle setEnabled method', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + // Test disabling + formItemList.setEnabled(false); + expect(formItemList.render.hasAttribute('disabled')).toBe(true); + + // Test enabling + formItemList.setEnabled(true); + expect(formItemList.render.hasAttribute('disabled')).toBe(false); + }); + + it('should handle persistent entries', () => { + const persistentValues: ListItemEntry[] = [ + { value: { field1: 'persistent1', field2: 'persistent2' }, persistent: true } + ]; + + formItemList = new FormItemListInternal({ + items: testFormItems, + value: persistentValues + }); + + document.body.appendChild(formItemList.render); + + // Persistent entries should have disabled remove buttons + const removeButtons = document.body.querySelectorAll('.mynah-form-item-list-row-remove-button'); + expect(removeButtons.length).toBeGreaterThanOrEqual(1); + }); + + it('should handle add button click', () => { + formItemList = new FormItemListInternal({ + items: testFormItems, + onChange: mockOnChange + }); + + document.body.appendChild(formItemList.render); + const addButton = document.body.querySelector('.mynah-form-item-list-add-button') as HTMLElement; + + const initialCallCount = mockOnChange.mock.calls.length; + addButton.click(); + + // Should trigger onChange when adding a row + expect(mockOnChange.mock.calls.length).toBeGreaterThan(initialCallCount); + }); + + it('should handle remove button click', () => { + formItemList = new FormItemListInternal({ + items: testFormItems, + onChange: mockOnChange + }); + + document.body.appendChild(formItemList.render); + const removeButton = document.body.querySelector('.mynah-form-item-list-row-remove-button') as HTMLElement; + + const initialCallCount = mockOnChange.mock.calls.length; + removeButton.click(); + + // Should trigger onChange when removing a row + expect(mockOnChange.mock.calls.length).toBeGreaterThan(initialCallCount); + }); + + it('should have rows wrapper', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + document.body.appendChild(formItemList.render); + const rowsWrapper = document.body.querySelector('.mynah-form-item-list-rows-wrapper'); + expect(rowsWrapper).toBeDefined(); + }); + + it('should have form item list wrapper', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + document.body.appendChild(formItemList.render); + const wrapper = document.body.querySelector('.mynah-form-item-list-wrapper'); + expect(wrapper).toBeDefined(); + }); + + it('should handle items without title or description', () => { + const itemsWithoutTitleDesc: SingularFormItem[] = [ + { + id: 'field1', + type: 'textinput' + } + ]; + + formItemList = new FormItemListInternal({ items: itemsWithoutTitleDesc }); + + document.body.appendChild(formItemList.render); + const headers = document.body.querySelectorAll('.mynah-form-item-list-row-header'); + + // Should have no headers when items have no title or description + expect(headers.length).toBe(0); + }); + }); + + describe('FormItemList Factory', () => { + it('should create FormItemListInternal by default', () => { + const formItemListFactory = new FormItemList({ items: testFormItems }); + expect(formItemListFactory).toBeInstanceOf(FormItemListInternal); + }); + + it('should have abstract methods', () => { + const formItemListFactory = new FormItemList({ items: testFormItems }); + expect(typeof formItemListFactory.setValue).toBe('function'); + expect(typeof formItemListFactory.getValue).toBe('function'); + expect(typeof formItemListFactory.setEnabled).toBe('function'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty items array', () => { + formItemList = new FormItemListInternal({ items: [] }); + expect(formItemList.render).toBeDefined(); + }); + + it('should handle null/undefined values gracefully', () => { + const props: FormItemListProps = { + items: testFormItems, + label: undefined, + description: undefined, + onChange: undefined + }; + + formItemList = new FormItemListInternal(props); + expect(formItemList.render).toBeDefined(); + }); + + it('should handle onChange without callback', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + // Should not throw error when onChange is not provided + document.body.appendChild(formItemList.render); + const addButton = document.body.querySelector('.mynah-form-item-list-add-button') as HTMLElement; + addButton.click(); + + expect(formItemList.render).toBeDefined(); + }); + + it('should handle setValue with undefined', () => { + formItemList = new FormItemListInternal({ items: testFormItems }); + + // Should handle undefined gracefully + formItemList.setValue([]); + expect(formItemList.render).toBeDefined(); + }); + + it('should handle complex form items', () => { + const complexItems: SingularFormItem[] = [ + { + id: 'field1', + type: 'select', + title: 'Select Field', + options: [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' } + ] + }, + { + id: 'field2', + type: 'radiogroup', + title: 'Radio Field', + options: [ + { value: 'radio1', label: 'Radio 1' }, + { value: 'radio2', label: 'Radio 2' } + ] + } + ]; + + formItemList = new FormItemListInternal({ items: complexItems }); + expect(formItemList.render).toBeDefined(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/form-item-pill-box.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/form-item-pill-box.spec.ts new file mode 100644 index 00000000..e121936c --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/form-item-pill-box.spec.ts @@ -0,0 +1,53 @@ +import { FormItemPillBox } from '../../../components/form-items/form-item-pill-box'; + +describe('FormItemPillBox', () => { + let pillBox: FormItemPillBox; + + beforeEach(() => { + pillBox = new FormItemPillBox({ + id: 'test-pill-box', + label: 'Test Pills', + placeholder: 'Add a pill' + }); + document.body.appendChild(pillBox.render); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + it('should render pill box', () => { + expect(pillBox.render).toBeDefined(); + expect(pillBox.render.querySelector('.mynah-form-item-pill-box-wrapper')).toBeTruthy(); + }); + + it('should add pill on enter', () => { + const input = pillBox.render.querySelector('.mynah-form-item-pill-box-input') as HTMLTextAreaElement; + input.value = 'test-pill'; + + const event = new KeyboardEvent('keydown', { key: 'Enter' }); + input.dispatchEvent(event); + + expect(pillBox.getValue()).toBe('test-pill'); + expect(pillBox.render.querySelector('.mynah-form-item-pill')).toBeTruthy(); + }); + + it('should remove pill on click', () => { + pillBox.setValue('pill1,pill2'); + + const removeButton = pillBox.render.querySelector('.mynah-form-item-pill-remove') as HTMLElement; + removeButton.click(); + + expect(pillBox.getValue()).toBe('pill2'); + }); + + it('should set and get values', () => { + pillBox.setValue('tag1,tag2,tag3'); + expect(pillBox.getValue()).toBe('tag1,tag2,tag3'); + }); + + it('should disable component', () => { + pillBox.setEnabled(false); + expect(pillBox.render.hasAttribute('disabled')).toBe(true); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/radio-group.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/radio-group.spec.ts new file mode 100644 index 00000000..a5df03ff --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/radio-group.spec.ts @@ -0,0 +1,416 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { RadioGroup, RadioGroupInternal, RadioGroupProps } from '../../../components/form-items/radio-group'; +import { MynahIcons } from '../../../components/icon'; +import { DomBuilder } from '../../../helper/dom'; + +// Mock the generateUID helper +jest.mock('../../../helper/guid', () => ({ + generateUID: jest.fn(() => 'test-group-id') +})); + +describe('RadioGroup Component', () => { + let radioGroup: RadioGroupInternal; + let mockOnChange: jest.Mock; + + const testOptions = [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + { value: 'option3', label: 'Option 3' } + ]; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('RadioGroupInternal', () => { + it('should create radio group with default props', () => { + radioGroup = new RadioGroupInternal({}); + + expect(radioGroup.render).toBeDefined(); + expect(radioGroup.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + expect(radioGroup.getValue()).toBe(''); + }); + + it('should create radio group with options', () => { + radioGroup = new RadioGroupInternal({ options: testOptions }); + + document.body.appendChild(radioGroup.render); + const radioInputs = document.body.querySelectorAll('input[type="radio"]'); + const labels = document.body.querySelectorAll('.mynah-form-input-radio-label'); + + expect(radioInputs).toHaveLength(3); + expect(labels).toHaveLength(3); + + expect(radioInputs[0].getAttribute('value')).toBe('option1'); + expect(radioInputs[1].getAttribute('value')).toBe('option2'); + expect(radioInputs[2].getAttribute('value')).toBe('option3'); + + expect(labels[0].textContent).toContain('Option 1'); + expect(labels[1].textContent).toContain('Option 2'); + expect(labels[2].textContent).toContain('Option 3'); + }); + + it('should create radio group with initial value', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + value: 'option2' + }); + + document.body.appendChild(radioGroup.render); + const checkedInput = document.body.querySelector('input[checked]') as HTMLInputElement; + + expect(checkedInput.value).toBe('option2'); + expect(radioGroup.getValue()).toBe('option2'); + }); + + it('should select first option by default when not optional and no value provided', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + optional: false + }); + + document.body.appendChild(radioGroup.render); + const checkedInput = document.body.querySelector('input[checked]') as HTMLInputElement; + + expect(checkedInput.value).toBe('option1'); + expect(radioGroup.getValue()).toBe('option1'); + }); + + it('should not select any option by default when optional', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + optional: true + }); + + document.body.appendChild(radioGroup.render); + const checkedInput = document.body.querySelector('input[checked]'); + + expect(checkedInput).toBeNull(); + expect(radioGroup.getValue()).toBe(''); + }); + + it('should create radio group with label', () => { + const label = 'Test Radio Group'; + radioGroup = new RadioGroupInternal({ label }); + + document.body.appendChild(radioGroup.render); + const labelElement = document.body.querySelector('.mynah-form-input-label'); + expect(labelElement?.textContent).toBe(label); + }); + + it('should create radio group with radiogroup type (default)', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + type: 'radiogroup' + }); + + document.body.appendChild(radioGroup.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.classList.contains('mynah-form-input-radio-group')).toBe(true); + }); + + it('should create radio group with toggle type', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + type: 'toggle' + }); + + document.body.appendChild(radioGroup.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.classList.contains('mynah-form-input-toggle-group')).toBe(true); + }); + + it('should create radio group with custom icons', () => { + const optionsWithIcons = [ + { value: 'option1', label: 'Option 1', icon: MynahIcons.OK }, + { value: 'option2', label: 'Option 2', icon: MynahIcons.CANCEL } + ]; + + radioGroup = new RadioGroupInternal({ options: optionsWithIcons }); + + document.body.appendChild(radioGroup.render); + // Check for radio check elements instead of icons since icons might not render in test environment + const radioChecks = document.body.querySelectorAll('.mynah-form-input-radio-check'); + expect(radioChecks).toHaveLength(2); + }); + + it('should create radio group with default DOT icon', () => { + radioGroup = new RadioGroupInternal({ options: testOptions }); + + document.body.appendChild(radioGroup.render); + // Check for radio check elements instead of icons since icons might not render in test environment + const radioChecks = document.body.querySelectorAll('.mynah-form-input-radio-check'); + expect(radioChecks).toHaveLength(3); + }); + + it('should create radio group with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + radioGroup = new RadioGroupInternal({ classNames: customClasses }); + + document.body.appendChild(radioGroup.render); + const radioGroupElement = document.body.querySelector('.mynah-form-input'); + expect(radioGroupElement?.classList.contains('custom-class-1')).toBe(true); + expect(radioGroupElement?.classList.contains('custom-class-2')).toBe(true); + }); + + it('should create radio group with custom attributes', () => { + const attributes = { 'data-test': 'test-value', 'aria-label': 'test-radio-group' }; + radioGroup = new RadioGroupInternal({ attributes }); + + document.body.appendChild(radioGroup.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.getAttribute('data-test')).toBe('test-value'); + expect(container?.getAttribute('aria-label')).toBe('test-radio-group'); + }); + + it('should create radio group with test IDs', () => { + const wrapperTestId = 'wrapper-test-id'; + const optionTestId = 'option-test-id'; + radioGroup = new RadioGroupInternal({ + options: testOptions, + wrapperTestId, + optionTestId + }); + + document.body.appendChild(radioGroup.render); + const wrapper = document.body.querySelector(`.mynah-form-input[data-testid="${wrapperTestId}"]`); + expect(wrapper).toBeDefined(); + + // Just verify labels exist even if test IDs don't work in test environment + const labels = document.body.querySelectorAll('label'); + expect(labels).toHaveLength(3); + }); + + it('should handle setValue method', () => { + radioGroup = new RadioGroupInternal({ options: testOptions }); + document.body.appendChild(radioGroup.render); + + radioGroup.setValue('option2'); + expect(radioGroup.getValue()).toBe('option2'); + + const checkedInput = document.body.querySelector('input[checked]') as HTMLInputElement; + expect(checkedInput.value).toBe('option2'); + }); + + it('should handle setValue by removing previous selection', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + value: 'option1' + }); + document.body.appendChild(radioGroup.render); + + // Initially option1 should be checked + let checkedInputs = document.body.querySelectorAll('input[checked]'); + expect(checkedInputs).toHaveLength(1); + expect((checkedInputs[0] as HTMLInputElement).value).toBe('option1'); + + // Set to option2 + radioGroup.setValue('option2'); + checkedInputs = document.body.querySelectorAll('input[checked]'); + expect(checkedInputs).toHaveLength(1); + expect((checkedInputs[0] as HTMLInputElement).value).toBe('option2'); + }); + + it('should handle setEnabled method', () => { + radioGroup = new RadioGroupInternal({ options: testOptions }); + document.body.appendChild(radioGroup.render); + + const radioGroupElement = document.body.querySelector('.mynah-form-input') as HTMLElement; + const radioInputs = document.body.querySelectorAll('input[type="radio"]'); + + // Test disabling + radioGroup.setEnabled(false); + expect(radioGroupElement.hasAttribute('disabled')).toBe(true); + radioInputs.forEach(input => { + expect(input.hasAttribute('disabled')).toBe(true); + }); + + // Test enabling + radioGroup.setEnabled(true); + expect(radioGroupElement.hasAttribute('disabled')).toBe(false); + radioInputs.forEach(input => { + expect(input.hasAttribute('disabled')).toBe(false); + }); + }); + + it('should trigger onChange when option is clicked', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + onChange: mockOnChange + }); + + document.body.appendChild(radioGroup.render); + const labels = document.body.querySelectorAll('.mynah-form-input-radio-label'); + + (labels[1] as HTMLElement).click(); + + expect(mockOnChange).toHaveBeenCalledWith('option2'); + expect(radioGroup.getValue()).toBe('option2'); + }); + + it('should prevent event propagation on click', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + onChange: mockOnChange + }); + + document.body.appendChild(radioGroup.render); + const label = document.body.querySelector('.mynah-form-input-radio-label') as HTMLElement; + const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true }); + + const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault'); + const stopPropagationSpy = jest.spyOn(clickEvent, 'stopPropagation'); + + label.dispatchEvent(clickEvent); + + expect(preventDefaultSpy).toHaveBeenCalled(); + expect(stopPropagationSpy).toHaveBeenCalled(); + }); + + it('should set radio input checked property on click', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + onChange: mockOnChange + }); + + document.body.appendChild(radioGroup.render); + const labels = document.body.querySelectorAll('.mynah-form-input-radio-label'); + const radioInputs = document.body.querySelectorAll('input[type="radio"]'); + + (labels[1] as HTMLElement).click(); + + expect((radioInputs[1] as HTMLInputElement).checked).toBe(true); + expect((radioInputs[0] as HTMLInputElement).checked).toBe(false); + expect((radioInputs[2] as HTMLInputElement).checked).toBe(false); + }); + + it('should handle description element', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + radioGroup = new RadioGroupInternal({ description: descriptionElement }); + document.body.appendChild(radioGroup.render); + + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should handle options without labels', () => { + const optionsWithoutLabels = [ + { value: 'option1' }, + { value: 'option2' } + ]; + + radioGroup = new RadioGroupInternal({ options: optionsWithoutLabels }); + + document.body.appendChild(radioGroup.render); + const labels = document.body.querySelectorAll('.mynah-form-input-radio-label'); + + expect(labels).toHaveLength(2); + // Should only contain radio input and icon, no text + expect(labels[0].children).toHaveLength(2); // input + icon span + expect(labels[1].children).toHaveLength(2); // input + icon span + }); + + it('should generate unique group names for radio inputs', () => { + radioGroup = new RadioGroupInternal({ options: testOptions }); + + document.body.appendChild(radioGroup.render); + const radioInputs = document.body.querySelectorAll('input[type="radio"]'); + + const groupName = (radioInputs[0] as HTMLInputElement).name; + expect(groupName).toBe('test-group-id'); + + radioInputs.forEach(input => { + expect((input as HTMLInputElement).name).toBe(groupName); + expect(input.id).toContain(groupName); + }); + }); + }); + + describe('RadioGroup Factory', () => { + it('should create RadioGroupInternal by default', () => { + const radioGroupFactory = new RadioGroup({}); + expect(radioGroupFactory).toBeInstanceOf(RadioGroupInternal); + }); + + it('should have abstract methods', () => { + const radioGroupFactory = new RadioGroup({}); + expect(typeof radioGroupFactory.setValue).toBe('function'); + expect(typeof radioGroupFactory.getValue).toBe('function'); + expect(typeof radioGroupFactory.setEnabled).toBe('function'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty props object', () => { + radioGroup = new RadioGroupInternal({}); + expect(radioGroup.render).toBeDefined(); + expect(radioGroup.getValue()).toBe(''); + }); + + it('should handle null/undefined values gracefully', () => { + const props: RadioGroupProps = { + label: undefined, + description: undefined, + options: undefined, + onChange: undefined + }; + + radioGroup = new RadioGroupInternal(props); + expect(radioGroup.render).toBeDefined(); + }); + + it('should handle empty options array', () => { + radioGroup = new RadioGroupInternal({ options: [] }); + + document.body.appendChild(radioGroup.render); + const radioInputs = document.body.querySelectorAll('input[type="radio"]'); + + expect(radioInputs).toHaveLength(0); + }); + + it('should handle setValue with non-existent value', () => { + radioGroup = new RadioGroupInternal({ options: testOptions }); + document.body.appendChild(radioGroup.render); + + radioGroup.setValue('non-existent'); + // Should not crash, but getValue should return empty string + expect(radioGroup.getValue()).toBe(''); + }); + + it('should handle onChange without callback', () => { + radioGroup = new RadioGroupInternal({ options: testOptions }); + + document.body.appendChild(radioGroup.render); + const label = document.body.querySelector('.mynah-form-input-radio-label') as HTMLElement; + + // Should not throw error + label.click(); + + expect(radioGroup.getValue()).toBe('option1'); + }); + + it('should handle getValue when no option is selected', () => { + radioGroup = new RadioGroupInternal({ + options: testOptions, + optional: true + }); + + document.body.appendChild(radioGroup.render); + expect(radioGroup.getValue()).toBe(''); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/select.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/select.spec.ts new file mode 100644 index 00000000..f7fa0eec --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/select.spec.ts @@ -0,0 +1,364 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Select, SelectInternal, SelectProps } from '../../../components/form-items/select'; +import { MynahIcons } from '../../../components/icon'; +import { DomBuilder } from '../../../helper/dom'; + +describe('Select Component', () => { + let select: SelectInternal; + let mockOnChange: jest.Mock; + + const testOptions = [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' }, + { value: 'option3', label: 'Option 3' } + ]; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('SelectInternal', () => { + it('should create select with default props', () => { + select = new SelectInternal({}); + + expect(select.render).toBeDefined(); + expect(select.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + expect(select.getValue()).toBe(''); + }); + + it('should create select with options', () => { + select = new SelectInternal({ options: testOptions }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + const options = selectElement.querySelectorAll('option'); + + expect(options).toHaveLength(3); + expect(options[0].value).toBe('option1'); + expect(options[0].textContent).toBe('Option 1'); + expect(options[1].value).toBe('option2'); + expect(options[1].textContent).toBe('Option 2'); + expect(options[2].value).toBe('option3'); + expect(options[2].textContent).toBe('Option 3'); + }); + + it('should create select with initial value', () => { + select = new SelectInternal({ + options: testOptions, + value: 'option2' + }); + + expect(select.getValue()).toBe('option2'); + }); + + it('should create select with label', () => { + const label = 'Test Select'; + select = new SelectInternal({ label }); + + document.body.appendChild(select.render); + const labelElement = document.body.querySelector('.mynah-form-input-label'); + expect(labelElement?.textContent).toBe(label); + }); + + it('should create select with placeholder and optional', () => { + const placeholder = 'Choose an option'; + select = new SelectInternal({ + options: testOptions, + placeholder, + optional: true + }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + const options = selectElement.querySelectorAll('option'); + + expect(options).toHaveLength(4); // 3 options + 1 placeholder + expect(options[0].value).toBe(''); + expect(options[0].textContent).toBe(placeholder); + expect(options[0].classList.contains('empty-option')).toBe(true); + }); + + it('should create select with default placeholder when optional', () => { + select = new SelectInternal({ + options: testOptions, + optional: true + }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + const firstOption = selectElement.querySelector('option'); + + expect(firstOption?.textContent).toBe('...'); + }); + + it('should create select with icon', () => { + select = new SelectInternal({ icon: MynahIcons.SEARCH }); + + document.body.appendChild(select.render); + const iconElement = document.body.querySelector('.mynah-form-input-icon'); + expect(iconElement).toBeDefined(); + }); + + it('should create select with custom handle icon', () => { + select = new SelectInternal({ handleIcon: MynahIcons.UP_OPEN }); + + document.body.appendChild(select.render); + const handleIcon = document.body.querySelector('.mynah-select-handle'); + expect(handleIcon).toBeDefined(); + }); + + it('should create select with default handle icon', () => { + select = new SelectInternal({}); + + document.body.appendChild(select.render); + const handleIcon = document.body.querySelector('.mynah-select-handle'); + expect(handleIcon).toBeDefined(); + }); + + it('should create select with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + select = new SelectInternal({ classNames: customClasses }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select'); + expect(selectElement?.classList.contains('custom-class-1')).toBe(true); + expect(selectElement?.classList.contains('custom-class-2')).toBe(true); + }); + + it('should create select with auto width', () => { + select = new SelectInternal({ + options: testOptions, + autoWidth: true, + value: 'option1' + }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select'); + const autoWidthSizer = document.body.querySelector('.select-auto-width-sizer'); + + expect(selectElement?.classList.contains('auto-width')).toBe(true); + expect(autoWidthSizer).toBeDefined(); + expect(autoWidthSizer?.textContent).toBe('Option 1'); + }); + + it('should create select without border', () => { + select = new SelectInternal({ border: false }); + + document.body.appendChild(select.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.classList.contains('no-border')).toBe(true); + }); + + it('should create select with custom attributes', () => { + const attributes = { 'data-test': 'test-value', 'aria-label': 'test-select' }; + select = new SelectInternal({ attributes }); + + document.body.appendChild(select.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.getAttribute('data-test')).toBe('test-value'); + expect(container?.getAttribute('aria-label')).toBe('test-select'); + }); + + it('should create select with test IDs', () => { + const wrapperTestId = 'wrapper-test-id'; + const optionTestId = 'option-test-id'; + select = new SelectInternal({ + options: testOptions, + wrapperTestId, + optionTestId + }); + + document.body.appendChild(select.render); + const wrapper = document.body.querySelector(`select[data-testid="${wrapperTestId}"]`); + // Just check that the select element has the test ID, options might not render test IDs in test environment + expect(wrapper).toBeDefined(); + + // Verify options exist even if test IDs don't work in test environment + const options = document.body.querySelectorAll('option'); + expect(options).toHaveLength(3); + }); + + it('should handle setValue method', () => { + select = new SelectInternal({ options: testOptions }); + + select.setValue('option2'); + expect(select.getValue()).toBe('option2'); + + select.setValue('option3'); + expect(select.getValue()).toBe('option3'); + }); + + it('should handle setValue with auto width', () => { + select = new SelectInternal({ + options: testOptions, + autoWidth: true + }); + + document.body.appendChild(select.render); + + select.setValue('option2'); + const autoWidthSizer = document.body.querySelector('.select-auto-width-sizer'); + expect(autoWidthSizer?.textContent).toBe('Option 2'); + }); + + it('should handle setEnabled method', () => { + select = new SelectInternal({}); + document.body.appendChild(select.render); + + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + + // Test disabling + select.setEnabled(false); + expect(selectElement.hasAttribute('disabled')).toBe(true); + + // Test enabling + select.setEnabled(true); + expect(selectElement.hasAttribute('disabled')).toBe(false); + }); + + it('should trigger onChange when selection changes', () => { + select = new SelectInternal({ + options: testOptions, + onChange: mockOnChange + }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + + selectElement.value = 'option2'; + selectElement.dispatchEvent(new Event('change')); + + expect(mockOnChange).toHaveBeenCalledWith('option2'); + }); + + it('should update auto width sizer on change', () => { + select = new SelectInternal({ + options: testOptions, + autoWidth: true, + onChange: mockOnChange + }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + const autoWidthSizer = document.body.querySelector('.select-auto-width-sizer'); + + selectElement.value = 'option2'; + selectElement.dispatchEvent(new Event('change')); + + expect(autoWidthSizer?.textContent).toBe('Option 2'); + }); + + it('should handle description element', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + select = new SelectInternal({ description: descriptionElement }); + document.body.appendChild(select.render); + + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should handle auto width sizer with placeholder when no value', () => { + const placeholder = 'Select option'; + select = new SelectInternal({ + options: testOptions, + autoWidth: true, + placeholder + }); + + document.body.appendChild(select.render); + const autoWidthSizer = document.body.querySelector('.select-auto-width-sizer'); + expect(autoWidthSizer?.textContent).toBe(placeholder); + }); + + it('should handle auto width sizer with empty string when no placeholder or value', () => { + select = new SelectInternal({ + options: testOptions, + autoWidth: true + }); + + document.body.appendChild(select.render); + const autoWidthSizer = document.body.querySelector('.select-auto-width-sizer'); + expect(autoWidthSizer?.textContent).toBe(''); + }); + }); + + describe('Select Factory', () => { + it('should create SelectInternal by default', () => { + const selectFactory = new Select({}); + expect(selectFactory).toBeInstanceOf(SelectInternal); + }); + + it('should have abstract methods', () => { + const selectFactory = new Select({}); + expect(typeof selectFactory.setValue).toBe('function'); + expect(typeof selectFactory.getValue).toBe('function'); + expect(typeof selectFactory.setEnabled).toBe('function'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty props object', () => { + select = new SelectInternal({}); + expect(select.render).toBeDefined(); + expect(select.getValue()).toBe(''); + }); + + it('should handle null/undefined values gracefully', () => { + const props: SelectProps = { + label: undefined, + description: undefined, + options: undefined, + onChange: undefined + }; + + select = new SelectInternal(props); + expect(select.render).toBeDefined(); + }); + + it('should handle empty options array', () => { + select = new SelectInternal({ options: [] }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + const options = selectElement.querySelectorAll('option'); + + expect(options).toHaveLength(0); + }); + + it('should handle setValue with non-existent value', () => { + select = new SelectInternal({ options: testOptions }); + + select.setValue('non-existent'); + // HTML select element will not set a value that doesn't exist in options + // So getValue should return empty string (default for no selection) + expect(select.getValue()).toBe(''); + }); + + it('should handle onChange without callback', () => { + select = new SelectInternal({ options: testOptions }); + + document.body.appendChild(select.render); + const selectElement = document.body.querySelector('select') as HTMLSelectElement; + + // Should not throw error + selectElement.value = 'option2'; + selectElement.dispatchEvent(new Event('change')); + + expect(select.getValue()).toBe('option2'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/stars.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/stars.spec.ts new file mode 100644 index 00000000..962ff975 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/stars.spec.ts @@ -0,0 +1,323 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Stars, StarsProps, StarValues } from '../../../components/form-items/stars'; +import { DomBuilder } from '../../../helper/dom'; + +describe('Stars Component', () => { + let stars: Stars; + let mockOnChange: jest.Mock; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('Stars', () => { + it('should create stars with default props', () => { + stars = new Stars({}); + + expect(stars.render).toBeDefined(); + expect(stars.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + expect(stars.getValue()).toBe(''); + }); + + it('should create 5 star elements', () => { + stars = new Stars({}); + + document.body.appendChild(stars.render); + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + expect(starElements).toHaveLength(5); + }); + + it('should create stars with initial value', () => { + stars = new Stars({ value: '3' }); + + document.body.appendChild(stars.render); + const selectedStar = document.body.querySelector('.mynah-feedback-form-star.selected'); + const starsContainer = document.body.querySelector('.mynah-feedback-form-stars-container'); + + expect(selectedStar).toBeDefined(); + expect(selectedStar?.getAttribute('star')).toBe('3'); + expect(starsContainer?.getAttribute('selected-star')).toBe('3'); + expect(stars.getValue()).toBe('3'); + }); + + it('should create stars with label', () => { + const label = 'Rate this'; + stars = new Stars({ label }); + + document.body.appendChild(stars.render); + const labelElement = document.body.querySelector('.mynah-form-input-label'); + expect(labelElement?.textContent).toBe(label); + }); + + it('should create stars with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + stars = new Stars({ classNames: customClasses }); + + document.body.appendChild(stars.render); + const starsInput = document.body.querySelector('.mynah-form-input'); + expect(starsInput?.classList.contains('custom-class-1')).toBe(true); + expect(starsInput?.classList.contains('custom-class-2')).toBe(true); + }); + + it('should create stars with custom attributes', () => { + const attributes = { 'data-test': 'test-value', 'aria-label': 'test-stars' }; + stars = new Stars({ attributes }); + + document.body.appendChild(stars.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.getAttribute('data-test')).toBe('test-value'); + expect(container?.getAttribute('aria-label')).toBe('test-stars'); + }); + + it('should create stars with test IDs', () => { + const wrapperTestId = 'wrapper-test-id'; + const optionTestId = 'option-test-id'; + stars = new Stars({ wrapperTestId, optionTestId }); + + document.body.appendChild(stars.render); + const wrapper = document.body.querySelector(`.mynah-feedback-form-stars-container[data-testid="${wrapperTestId}"]`); + expect(wrapper).toBeDefined(); + + // Just verify star elements exist even if test IDs don't work in test environment + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + expect(starElements).toHaveLength(5); + }); + + it('should handle setValue method', () => { + stars = new Stars({}); + + stars.setValue(4); + expect(stars.getValue()).toBe('4'); + + stars.setValue(2); + expect(stars.getValue()).toBe('2'); + }); + + it('should handle setEnabled method', () => { + stars = new Stars({}); + document.body.appendChild(stars.render); + + const starsInput = document.body.querySelector('.mynah-form-input') as HTMLElement; + + // Test disabling + stars.setEnabled(false); + expect(starsInput.hasAttribute('disabled')).toBe(true); + + // Test enabling + stars.setEnabled(true); + expect(starsInput.hasAttribute('disabled')).toBe(false); + }); + + it('should trigger onChange when star is clicked', () => { + stars = new Stars({ onChange: mockOnChange }); + + document.body.appendChild(stars.render); + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + // Click on the 3rd star (index 2) + (starElements[2] as HTMLElement).click(); + + expect(mockOnChange).toHaveBeenCalledWith('3'); + expect(stars.getValue()).toBe('3'); + }); + + it('should update selected star when clicked', () => { + stars = new Stars({ onChange: mockOnChange }); + + document.body.appendChild(stars.render); + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + // Click on the 4th star (index 3) + (starElements[3] as HTMLElement).click(); + + const selectedStar = document.body.querySelector('.mynah-feedback-form-star.selected'); + expect(selectedStar).toBe(starElements[3]); + expect(selectedStar?.getAttribute('star')).toBe('4'); + }); + + it('should remove previous selection when new star is clicked', () => { + stars = new Stars({ value: '2', onChange: mockOnChange }); + + document.body.appendChild(stars.render); + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + // Initially star 2 should be selected + let selectedStars = document.body.querySelectorAll('.mynah-feedback-form-star.selected'); + expect(selectedStars).toHaveLength(1); + expect(selectedStars[0].getAttribute('star')).toBe('2'); + + // Click on star 5 + (starElements[4] as HTMLElement).click(); + + // Now only star 5 should be selected + selectedStars = document.body.querySelectorAll('.mynah-feedback-form-star.selected'); + expect(selectedStars).toHaveLength(1); + expect(selectedStars[0].getAttribute('star')).toBe('5'); + }); + + it('should have correct star attributes', () => { + stars = new Stars({}); + + document.body.appendChild(stars.render); + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + starElements.forEach((star, index) => { + expect(star.getAttribute('star')).toBe((index + 1).toString()); + }); + }); + + it('should contain star icons', () => { + stars = new Stars({}); + + document.body.appendChild(stars.render); + // Check for star elements instead of icons since icons might not render in test environment + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + expect(starElements).toHaveLength(5); + }); + + it('should handle description element', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + stars = new Stars({ description: descriptionElement }); + document.body.appendChild(stars.render); + + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should have no-border class on container', () => { + stars = new Stars({}); + + document.body.appendChild(stars.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.classList.contains('no-border')).toBe(true); + }); + + it('should handle multiple clicks on same star', () => { + stars = new Stars({ onChange: mockOnChange }); + + document.body.appendChild(stars.render); + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + // Click on the same star multiple times + (starElements[2] as HTMLElement).click(); + (starElements[2] as HTMLElement).click(); + (starElements[2] as HTMLElement).click(); + + expect(mockOnChange).toHaveBeenCalledTimes(3); + expect(mockOnChange).toHaveBeenCalledWith('3'); + expect(stars.getValue()).toBe('3'); + }); + + it('should handle clicks on different stars', () => { + stars = new Stars({ onChange: mockOnChange }); + + document.body.appendChild(stars.render); + const starElements = document.body.querySelectorAll('.mynah-feedback-form-star'); + + // Click on different stars + (starElements[0] as HTMLElement).click(); // Star 1 + (starElements[2] as HTMLElement).click(); // Star 3 + (starElements[4] as HTMLElement).click(); // Star 5 + + expect(mockOnChange).toHaveBeenCalledTimes(3); + expect(mockOnChange).toHaveBeenNthCalledWith(1, '1'); + expect(mockOnChange).toHaveBeenNthCalledWith(2, '3'); + expect(mockOnChange).toHaveBeenNthCalledWith(3, '5'); + expect(stars.getValue()).toBe('5'); + }); + + it('should handle initStar property (legacy)', () => { + // initStar is defined in the interface but not used in implementation + stars = new Stars({ initStar: 3 }); + expect(stars.render).toBeDefined(); + // Since initStar is not implemented, getValue should return empty string + expect(stars.getValue()).toBe(''); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty props object', () => { + stars = new Stars({}); + expect(stars.render).toBeDefined(); + expect(stars.getValue()).toBe(''); + }); + + it('should handle null/undefined values gracefully', () => { + const props: StarsProps = { + label: undefined, + description: undefined, + onChange: undefined + }; + + stars = new Stars(props); + expect(stars.render).toBeDefined(); + }); + + it('should handle onChange without callback', () => { + stars = new Stars({}); + + document.body.appendChild(stars.render); + const starElement = document.body.querySelector('.mynah-feedback-form-star') as HTMLElement; + + // Should not throw error + starElement.click(); + + expect(stars.getValue()).toBe('1'); + }); + + it('should handle setValue with all valid star values', () => { + stars = new Stars({}); + + const validValues: StarValues[] = [ 1, 2, 3, 4, 5 ]; + + validValues.forEach(value => { + stars.setValue(value); + expect(stars.getValue()).toBe(value.toString()); + }); + }); + + it('should handle getValue when no star is selected', () => { + stars = new Stars({}); + expect(stars.getValue()).toBe(''); + }); + + it('should handle click events properly', () => { + stars = new Stars({ onChange: mockOnChange }); + + document.body.appendChild(stars.render); + const starElement = document.body.querySelector('.mynah-feedback-form-star') as HTMLElement; + + // Create and dispatch a click event + const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true }); + starElement.dispatchEvent(clickEvent); + + expect(mockOnChange).toHaveBeenCalledWith('1'); + }); + + it('should handle setEnabled when parent element exists', () => { + stars = new Stars({}); + document.body.appendChild(stars.render); + + // Test that setEnabled works when parent element is available + stars.setEnabled(false); + const starsInput = document.body.querySelector('.mynah-form-input') as HTMLElement; + expect(starsInput.hasAttribute('disabled')).toBe(true); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/switch.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/switch.spec.ts new file mode 100644 index 00000000..d522d08d --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/switch.spec.ts @@ -0,0 +1,320 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Switch, SwitchInternal, SwitchProps } from '../../../components/form-items/switch'; +import { MynahIcons } from '../../../components/icon'; +import { DomBuilder } from '../../../helper/dom'; + +describe('Switch Component', () => { + let switchComponent: SwitchInternal; + let mockOnChange: jest.Mock; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('SwitchInternal', () => { + it('should create switch with default props', () => { + switchComponent = new SwitchInternal({}); + + expect(switchComponent.render).toBeDefined(); + expect(switchComponent.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + expect(switchComponent.getValue()).toBe('false'); + }); + + it('should create switch with initial value true', () => { + switchComponent = new SwitchInternal({ value: 'true' }); + + expect(switchComponent.getValue()).toBe('true'); + }); + + it('should create switch with label', () => { + const label = 'Test Switch'; + switchComponent = new SwitchInternal({ label }); + + document.body.appendChild(switchComponent.render); + const switchWrapper = document.body.querySelector('.mynah-form-input-switch-wrapper'); + expect(switchWrapper?.textContent).toContain(label); + }); + + it('should create switch with title', () => { + const title = 'Switch Title'; + switchComponent = new SwitchInternal({ title }); + + document.body.appendChild(switchComponent.render); + const titleElement = document.body.querySelector('.mynah-form-input-label'); + expect(titleElement?.textContent).toBe(title); + }); + + it('should create switch with custom icon', () => { + switchComponent = new SwitchInternal({ icon: MynahIcons.CANCEL }); + + document.body.appendChild(switchComponent.render); + const iconElement = document.body.querySelector('.mynah-icon'); + expect(iconElement).toBeDefined(); + }); + + it('should create switch with default OK icon', () => { + switchComponent = new SwitchInternal({}); + + document.body.appendChild(switchComponent.render); + const iconElement = document.body.querySelector('.mynah-icon'); + expect(iconElement).toBeDefined(); + }); + + it('should create switch with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + switchComponent = new SwitchInternal({ classNames: customClasses }); + + document.body.appendChild(switchComponent.render); + const switchInput = document.body.querySelector('.mynah-form-input'); + expect(switchInput?.classList.contains('custom-class-1')).toBe(true); + expect(switchInput?.classList.contains('custom-class-2')).toBe(true); + }); + + it('should create switch with custom attributes', () => { + const attributes = { 'data-test': 'test-value', 'aria-label': 'test-switch' }; + switchComponent = new SwitchInternal({ attributes }); + + document.body.appendChild(switchComponent.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.getAttribute('data-test')).toBe('test-value'); + expect(container?.getAttribute('aria-label')).toBe('test-switch'); + }); + + it('should create switch with test ID', () => { + const testId = 'test-switch-id'; + switchComponent = new SwitchInternal({ testId }); + + document.body.appendChild(switchComponent.render); + const switchElement = document.body.querySelector(`[data-testid="${testId}"]`); + expect(switchElement).toBeDefined(); + }); + + it('should handle setValue method', () => { + switchComponent = new SwitchInternal({}); + + switchComponent.setValue('true'); + expect(switchComponent.getValue()).toBe('true'); + + switchComponent.setValue('false'); + expect(switchComponent.getValue()).toBe('false'); + }); + + it('should handle setEnabled method', () => { + switchComponent = new SwitchInternal({}); + document.body.appendChild(switchComponent.render); + + const checkboxInput = document.body.querySelector('input[type="checkbox"]') as HTMLInputElement; + const wrapper = document.body.querySelector('.mynah-form-input') as HTMLElement; + + // Test disabling + switchComponent.setEnabled(false); + expect(checkboxInput.hasAttribute('disabled')).toBe(true); + expect(wrapper.hasAttribute('disabled')).toBe(true); + + // Test enabling + switchComponent.setEnabled(true); + expect(checkboxInput.hasAttribute('disabled')).toBe(false); + expect(wrapper.hasAttribute('disabled')).toBe(false); + }); + + it('should trigger onChange when clicked', () => { + switchComponent = new SwitchInternal({ onChange: mockOnChange }); + document.body.appendChild(switchComponent.render); + + const label = document.body.querySelector('.mynah-form-input-switch-label') as HTMLElement; + + // Click to turn on + label.click(); + expect(mockOnChange).toHaveBeenCalledWith('true'); + expect(switchComponent.getValue()).toBe('true'); + + // Click to turn off + label.click(); + expect(mockOnChange).toHaveBeenCalledWith('false'); + expect(switchComponent.getValue()).toBe('false'); + }); + + it('should prevent event propagation on click', () => { + switchComponent = new SwitchInternal({ onChange: mockOnChange }); + document.body.appendChild(switchComponent.render); + + const label = document.body.querySelector('.mynah-form-input-switch-label') as HTMLElement; + const clickEvent = new MouseEvent('click', { bubbles: true, cancelable: true }); + + const preventDefaultSpy = jest.spyOn(clickEvent, 'preventDefault'); + const stopPropagationSpy = jest.spyOn(clickEvent, 'stopPropagation'); + + label.dispatchEvent(clickEvent); + + expect(preventDefaultSpy).toHaveBeenCalled(); + expect(stopPropagationSpy).toHaveBeenCalled(); + }); + + it('should have switch-specific elements', () => { + switchComponent = new SwitchInternal({}); + document.body.appendChild(switchComponent.render); + + const switchWrapper = document.body.querySelector('.mynah-form-input-switch-wrapper'); + const switchLabel = document.body.querySelector('.mynah-form-input-switch-label'); + const switchCheck = document.body.querySelector('.mynah-form-input-switch-check'); + const switchBg = document.body.querySelector('.mynah-form-input-switch-check-bg'); + + expect(switchWrapper).toBeDefined(); + expect(switchLabel).toBeDefined(); + expect(switchCheck).toBeDefined(); + expect(switchBg).toBeDefined(); + }); + + it('should contain checkbox input', () => { + switchComponent = new SwitchInternal({}); + document.body.appendChild(switchComponent.render); + + const checkboxInput = document.body.querySelector('input[type="checkbox"]'); + expect(checkboxInput).toBeDefined(); + }); + + it('should handle description element', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + switchComponent = new SwitchInternal({ description: descriptionElement }); + document.body.appendChild(switchComponent.render); + + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should have no-border class on container', () => { + switchComponent = new SwitchInternal({}); + + document.body.appendChild(switchComponent.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.classList.contains('no-border')).toBe(true); + }); + + it('should handle optional property', () => { + switchComponent = new SwitchInternal({ optional: true }); + + // The optional property is passed but doesn't affect rendering in current implementation + expect(switchComponent.render).toBeDefined(); + }); + + it('should toggle state correctly on multiple clicks', () => { + switchComponent = new SwitchInternal({ onChange: mockOnChange }); + document.body.appendChild(switchComponent.render); + + const label = document.body.querySelector('.mynah-form-input-switch-label') as HTMLElement; + + // Multiple clicks + label.click(); // false -> true + label.click(); // true -> false + label.click(); // false -> true + label.click(); // true -> false + + expect(mockOnChange).toHaveBeenCalledTimes(4); + expect(mockOnChange).toHaveBeenNthCalledWith(1, 'true'); + expect(mockOnChange).toHaveBeenNthCalledWith(2, 'false'); + expect(mockOnChange).toHaveBeenNthCalledWith(3, 'true'); + expect(mockOnChange).toHaveBeenNthCalledWith(4, 'false'); + expect(switchComponent.getValue()).toBe('false'); + }); + + it('should start with correct initial state', () => { + switchComponent = new SwitchInternal({ value: 'true' }); + document.body.appendChild(switchComponent.render); + + const checkboxInput = document.body.querySelector('input[type="checkbox"]') as HTMLInputElement; + expect(checkboxInput.checked).toBe(true); + expect(switchComponent.getValue()).toBe('true'); + }); + }); + + describe('Switch Factory', () => { + it('should create SwitchInternal by default', () => { + const switchFactory = new Switch({}); + expect(switchFactory).toBeInstanceOf(SwitchInternal); + }); + + it('should have abstract methods', () => { + const switchFactory = new Switch({}); + expect(typeof switchFactory.setValue).toBe('function'); + expect(typeof switchFactory.getValue).toBe('function'); + expect(typeof switchFactory.setEnabled).toBe('function'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty props object', () => { + switchComponent = new SwitchInternal({}); + expect(switchComponent.render).toBeDefined(); + expect(switchComponent.getValue()).toBe('false'); + }); + + it('should handle null/undefined values gracefully', () => { + const props: SwitchProps = { + title: undefined, + label: undefined, + description: undefined, + onChange: undefined + }; + + switchComponent = new SwitchInternal(props); + expect(switchComponent.render).toBeDefined(); + }); + + it('should handle onChange without callback', () => { + switchComponent = new SwitchInternal({}); + + document.body.appendChild(switchComponent.render); + const label = document.body.querySelector('.mynah-form-input-switch-label') as HTMLElement; + + // Should not throw error + label.click(); + + expect(switchComponent.getValue()).toBe('true'); + }); + + it('should handle setValue with string boolean values', () => { + switchComponent = new SwitchInternal({}); + + switchComponent.setValue('true'); + expect(switchComponent.getValue()).toBe('true'); + + switchComponent.setValue('false'); + expect(switchComponent.getValue()).toBe('false'); + }); + + it('should maintain state consistency', () => { + switchComponent = new SwitchInternal({ value: 'false' }); + document.body.appendChild(switchComponent.render); + + const checkboxInput = document.body.querySelector('input[type="checkbox"]') as HTMLInputElement; + + // Initial state + expect(checkboxInput.checked).toBe(false); + expect(switchComponent.getValue()).toBe('false'); + + // Set to true + switchComponent.setValue('true'); + expect(checkboxInput.checked).toBe(true); + expect(switchComponent.getValue()).toBe('true'); + + // Set back to false + switchComponent.setValue('false'); + expect(checkboxInput.checked).toBe(false); + expect(switchComponent.getValue()).toBe('false'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/text-area.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/text-area.spec.ts new file mode 100644 index 00000000..da21a919 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/text-area.spec.ts @@ -0,0 +1,368 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TextArea, TextAreaInternal, TextAreaProps } from '../../../components/form-items/text-area'; +import { DomBuilder } from '../../../helper/dom'; + +// Mock the validator helper +jest.mock('../../../helper/validator', () => ({ + checkTextElementValidation: jest.fn() +})); + +describe('TextArea Component', () => { + let textArea: TextAreaInternal; + let mockOnChange: jest.Mock; + let mockOnKeyPress: jest.Mock; + let mockFireModifierAndEnterKeyPress: jest.Mock; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + mockOnKeyPress = jest.fn(); + mockFireModifierAndEnterKeyPress = jest.fn(); + jest.clearAllMocks(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('TextAreaInternal', () => { + it('should create text area with default props', () => { + textArea = new TextAreaInternal({}); + + expect(textArea.render).toBeDefined(); + expect(textArea.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + expect(textArea.getValue()).toBe(''); + }); + + it('should create text area with initial value', () => { + const initialValue = 'test value'; + textArea = new TextAreaInternal({ value: initialValue }); + + expect(textArea.getValue()).toBe(initialValue); + }); + + it('should create text area with label', () => { + const label = 'Test Label'; + textArea = new TextAreaInternal({ label }); + + document.body.appendChild(textArea.render); + const labelElement = document.body.querySelector('.mynah-form-input-label'); + expect(labelElement?.textContent).toBe(label); + }); + + it('should create text area with placeholder', () => { + const placeholder = 'Enter text here'; + textArea = new TextAreaInternal({ placeholder }); + + document.body.appendChild(textArea.render); + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + expect(textAreaElement.placeholder).toBe(placeholder); + }); + + it('should create text area with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + textArea = new TextAreaInternal({ classNames: customClasses }); + + document.body.appendChild(textArea.render); + const textAreaElement = document.body.querySelector('textarea'); + expect(textAreaElement?.classList.contains('custom-class-1')).toBe(true); + expect(textAreaElement?.classList.contains('custom-class-2')).toBe(true); + }); + + it('should create text area with custom attributes', () => { + const attributes = { 'data-test': 'test-value', 'aria-label': 'test-textarea' }; + textArea = new TextAreaInternal({ attributes }); + + document.body.appendChild(textArea.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.getAttribute('data-test')).toBe('test-value'); + expect(container?.getAttribute('aria-label')).toBe('test-textarea'); + }); + + it('should create text area with test ID', () => { + const testId = 'test-textarea-id'; + textArea = new TextAreaInternal({ testId }); + + document.body.appendChild(textArea.render); + const textAreaElement = document.body.querySelector(`[data-testid="${testId}"]`); + expect(textAreaElement).toBeDefined(); + }); + + it('should handle autoFocus', (done) => { + textArea = new TextAreaInternal({ autoFocus: true }); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + + // Check that autofocus attribute is set + expect(textAreaElement.hasAttribute('autofocus')).toBe(true); + + // Check that focus is called after timeout + setTimeout(() => { + expect(document.activeElement).toBe(textAreaElement); + done(); + }, 300); + }); + + it('should handle setValue and getValue methods', () => { + textArea = new TextAreaInternal({}); + + const testValue = 'test value'; + textArea.setValue(testValue); + expect(textArea.getValue()).toBe(testValue); + }); + + it('should handle setEnabled method', () => { + textArea = new TextAreaInternal({}); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + + // Test disabling + textArea.setEnabled(false); + expect(textAreaElement.hasAttribute('disabled')).toBe(true); + + // Test enabling + textArea.setEnabled(true); + expect(textAreaElement.hasAttribute('disabled')).toBe(false); + }); + + it('should trigger onChange on keyup event', () => { + textArea = new TextAreaInternal({ onChange: mockOnChange }); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + const testValue = 'test input'; + + // Set value and trigger keyup event with proper target + textAreaElement.value = testValue; + const keyupEvent = new KeyboardEvent('keyup', { bubbles: true }); + Object.defineProperty(keyupEvent, 'currentTarget', { + value: textAreaElement, + enumerable: true + }); + textAreaElement.dispatchEvent(keyupEvent); + + expect(mockOnChange).toHaveBeenCalledWith(testValue); + }); + + it('should trigger onKeyPress on keypress event', () => { + textArea = new TextAreaInternal({ onKeyPress: mockOnKeyPress }); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + const keyEvent = new KeyboardEvent('keypress', { key: 'a' }); + + textAreaElement.dispatchEvent(keyEvent); + + expect(mockOnKeyPress).toHaveBeenCalledWith(keyEvent); + }); + + it('should trigger fireModifierAndEnterKeyPress on Ctrl+Enter', () => { + textArea = new TextAreaInternal({ fireModifierAndEnterKeyPress: mockFireModifierAndEnterKeyPress }); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }); + + textAreaElement.dispatchEvent(keyEvent); + + expect(mockFireModifierAndEnterKeyPress).toHaveBeenCalled(); + }); + + it('should trigger fireModifierAndEnterKeyPress on Cmd+Enter (Mac)', () => { + textArea = new TextAreaInternal({ fireModifierAndEnterKeyPress: mockFireModifierAndEnterKeyPress }); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter', metaKey: true }); + + textAreaElement.dispatchEvent(keyEvent); + + expect(mockFireModifierAndEnterKeyPress).toHaveBeenCalled(); + }); + + it('should not trigger fireModifierAndEnterKeyPress on Enter without modifier', () => { + textArea = new TextAreaInternal({ fireModifierAndEnterKeyPress: mockFireModifierAndEnterKeyPress }); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter' }); + + textAreaElement.dispatchEvent(keyEvent); + + expect(mockFireModifierAndEnterKeyPress).not.toHaveBeenCalled(); + }); + + it('should call checkValidation on blur event', () => { + const checkTextElementValidation = jest.requireMock('../../../helper/validator').checkTextElementValidation; + textArea = new TextAreaInternal({}); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + textAreaElement.dispatchEvent(new Event('blur')); + + expect(checkTextElementValidation).toHaveBeenCalled(); + }); + + it('should call checkValidation on keyup event', () => { + const checkTextElementValidation = jest.requireMock('../../../helper/validator').checkTextElementValidation; + textArea = new TextAreaInternal({}); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + const keyupEvent = new KeyboardEvent('keyup', { bubbles: true }); + Object.defineProperty(keyupEvent, 'currentTarget', { + value: textAreaElement, + enumerable: true + }); + textAreaElement.dispatchEvent(keyupEvent); + + expect(checkTextElementValidation).toHaveBeenCalled(); + }); + + it('should handle validation patterns', () => { + const validationPatterns = { + operator: 'and' as const, + patterns: [ { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, errorMessage: 'Invalid email' } ] + }; + + textArea = new TextAreaInternal({ validationPatterns }); + expect(textArea.render).toBeDefined(); + }); + + it('should handle mandatory field', () => { + textArea = new TextAreaInternal({ mandatory: true }); + expect(textArea.render).toBeDefined(); + }); + + it('should handle description element', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + textArea = new TextAreaInternal({ description: descriptionElement }); + document.body.appendChild(textArea.render); + + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should have validation error block', () => { + textArea = new TextAreaInternal({}); + document.body.appendChild(textArea.render); + + const errorBlock = document.body.querySelector('.mynah-form-input-validation-error-block'); + expect(errorBlock).toBeDefined(); + }); + + it('should create textarea element with correct tag', () => { + textArea = new TextAreaInternal({}); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea'); + expect(textAreaElement).toBeDefined(); + expect(textAreaElement?.tagName.toLowerCase()).toBe('textarea'); + }); + + it('should have mynah-form-input class', () => { + textArea = new TextAreaInternal({}); + document.body.appendChild(textArea.render); + + const textAreaElement = document.body.querySelector('textarea'); + expect(textAreaElement?.classList.contains('mynah-form-input')).toBe(true); + }); + }); + + describe('TextArea Factory', () => { + it('should create TextAreaInternal by default', () => { + const textAreaFactory = new TextArea({}); + expect(textAreaFactory).toBeInstanceOf(TextAreaInternal); + }); + + it('should have abstract methods', () => { + const textAreaFactory = new TextArea({}); + expect(typeof textAreaFactory.setValue).toBe('function'); + expect(typeof textAreaFactory.getValue).toBe('function'); + expect(typeof textAreaFactory.setEnabled).toBe('function'); + expect(typeof textAreaFactory.checkValidation).toBe('function'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty props object', () => { + textArea = new TextAreaInternal({}); + expect(textArea.render).toBeDefined(); + expect(textArea.getValue()).toBe(''); + }); + + it('should handle null/undefined values gracefully', () => { + const props: TextAreaProps = { + label: undefined, + description: undefined, + onChange: undefined, + onKeyPress: undefined, + fireModifierAndEnterKeyPress: undefined + }; + + textArea = new TextAreaInternal(props); + expect(textArea.render).toBeDefined(); + }); + + it('should handle empty string value', () => { + textArea = new TextAreaInternal({ value: '' }); + expect(textArea.getValue()).toBe(''); + }); + + it('should handle multiline text value', () => { + const multilineValue = 'Line 1\nLine 2\nLine 3'; + textArea = new TextAreaInternal({ value: multilineValue }); + expect(textArea.getValue()).toBe(multilineValue); + }); + + it('should handle onChange without callback', () => { + textArea = new TextAreaInternal({}); + + document.body.appendChild(textArea.render); + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + + // Should not throw error + textAreaElement.value = 'test'; + textAreaElement.dispatchEvent(new Event('input')); + + expect(textArea.getValue()).toBe('test'); + }); + + it('should handle onKeyPress without callback', () => { + textArea = new TextAreaInternal({}); + + document.body.appendChild(textArea.render); + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + + // Should not throw error + const keyEvent = new KeyboardEvent('keypress', { key: 'a' }); + textAreaElement.dispatchEvent(keyEvent); + + expect(textArea.render).toBeDefined(); + }); + + it('should handle fireModifierAndEnterKeyPress without callback', () => { + textArea = new TextAreaInternal({}); + + document.body.appendChild(textArea.render); + const textAreaElement = document.body.querySelector('textarea') as HTMLTextAreaElement; + + // Should not throw error + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }); + textAreaElement.dispatchEvent(keyEvent); + + expect(textArea.render).toBeDefined(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/form-items/text-input.spec.ts b/vendor/mynah-ui/src/__test__/components/form-items/text-input.spec.ts new file mode 100644 index 00000000..289ff1da --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/form-items/text-input.spec.ts @@ -0,0 +1,323 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { TextInput, TextInputInternal, TextInputProps } from '../../../components/form-items/text-input'; +import { MynahIcons } from '../../../components/icon'; +import { DomBuilder } from '../../../helper/dom'; + +// Mock the validator helper +jest.mock('../../../helper/validator', () => ({ + checkTextElementValidation: jest.fn() +})); + +describe('TextInput Component', () => { + let textInput: TextInputInternal; + let mockOnChange: jest.Mock; + let mockOnKeyPress: jest.Mock; + let mockFireModifierAndEnterKeyPress: jest.Mock; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnChange = jest.fn(); + mockOnKeyPress = jest.fn(); + mockFireModifierAndEnterKeyPress = jest.fn(); + jest.clearAllMocks(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('TextInputInternal', () => { + it('should create text input with default props', () => { + textInput = new TextInputInternal({}); + + expect(textInput.render).toBeDefined(); + expect(textInput.render.classList.contains('mynah-form-input-wrapper')).toBe(true); + expect(textInput.getValue()).toBe(''); + }); + + it('should create text input with initial value', () => { + const initialValue = 'test value'; + textInput = new TextInputInternal({ value: initialValue }); + + expect(textInput.getValue()).toBe(initialValue); + }); + + it('should create text input with label', () => { + const label = 'Test Label'; + textInput = new TextInputInternal({ label }); + + document.body.appendChild(textInput.render); + const labelElement = document.body.querySelector('.mynah-form-input-label'); + expect(labelElement?.textContent).toBe(label); + }); + + it('should create text input with placeholder', () => { + const placeholder = 'Enter text here'; + textInput = new TextInputInternal({ placeholder }); + + document.body.appendChild(textInput.render); + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + expect(inputElement.placeholder).toBe(placeholder); + }); + + it('should create text input with different types', () => { + const types: Array<'text' | 'number' | 'email'> = [ 'text', 'number', 'email' ]; + + types.forEach(type => { + textInput = new TextInputInternal({ type }); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + expect(inputElement.type).toBe(type); + + document.body.innerHTML = ''; + }); + }); + + it('should create text input with icon', () => { + textInput = new TextInputInternal({ icon: MynahIcons.SEARCH }); + + document.body.appendChild(textInput.render); + const iconElement = document.body.querySelector('.mynah-form-input-icon'); + expect(iconElement).toBeDefined(); + }); + + it('should create text input with custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + textInput = new TextInputInternal({ classNames: customClasses }); + + document.body.appendChild(textInput.render); + const inputElement = document.body.querySelector('.mynah-form-input'); + expect(inputElement?.classList.contains('custom-class-1')).toBe(true); + expect(inputElement?.classList.contains('custom-class-2')).toBe(true); + }); + + it('should create text input with custom attributes', () => { + const attributes = { 'data-test': 'test-value', 'aria-label': 'test-input' }; + textInput = new TextInputInternal({ attributes }); + + document.body.appendChild(textInput.render); + const container = document.body.querySelector('.mynah-form-input-container'); + expect(container?.getAttribute('data-test')).toBe('test-value'); + expect(container?.getAttribute('aria-label')).toBe('test-input'); + }); + + it('should create text input with test ID', () => { + const testId = 'test-input-id'; + textInput = new TextInputInternal({ testId }); + + document.body.appendChild(textInput.render); + const inputElement = document.body.querySelector(`[data-testid="${testId}"]`); + expect(inputElement).toBeDefined(); + }); + + it('should handle autoFocus', (done) => { + textInput = new TextInputInternal({ autoFocus: true }); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + + // Check that autofocus attribute is set + expect(inputElement.hasAttribute('autofocus')).toBe(true); + + // Check that focus is called after timeout + setTimeout(() => { + expect(document.activeElement).toBe(inputElement); + done(); + }, 300); + }); + + it('should handle setValue and getValue methods', () => { + textInput = new TextInputInternal({}); + + const testValue = 'test value'; + textInput.setValue(testValue); + expect(textInput.getValue()).toBe(testValue); + }); + + it('should handle setEnabled method', () => { + textInput = new TextInputInternal({}); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + + // Test disabling + textInput.setEnabled(false); + expect(inputElement.hasAttribute('disabled')).toBe(true); + + // Test enabling + textInput.setEnabled(true); + expect(inputElement.hasAttribute('disabled')).toBe(false); + }); + + it('should trigger onChange on input event', () => { + textInput = new TextInputInternal({ onChange: mockOnChange }); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + const testValue = 'test input'; + + inputElement.value = testValue; + inputElement.dispatchEvent(new Event('input')); + + expect(mockOnChange).toHaveBeenCalledWith(testValue); + }); + + it('should trigger onKeyPress on keypress event', () => { + textInput = new TextInputInternal({ onKeyPress: mockOnKeyPress }); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + const keyEvent = new KeyboardEvent('keypress', { key: 'a' }); + + inputElement.dispatchEvent(keyEvent); + + expect(mockOnKeyPress).toHaveBeenCalledWith(keyEvent); + }); + + it('should trigger fireModifierAndEnterKeyPress on Ctrl+Enter', () => { + textInput = new TextInputInternal({ fireModifierAndEnterKeyPress: mockFireModifierAndEnterKeyPress }); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter', ctrlKey: true }); + + inputElement.dispatchEvent(keyEvent); + + expect(mockFireModifierAndEnterKeyPress).toHaveBeenCalled(); + }); + + it('should trigger fireModifierAndEnterKeyPress on Cmd+Enter (Mac)', () => { + textInput = new TextInputInternal({ fireModifierAndEnterKeyPress: mockFireModifierAndEnterKeyPress }); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter', metaKey: true }); + + inputElement.dispatchEvent(keyEvent); + + expect(mockFireModifierAndEnterKeyPress).toHaveBeenCalled(); + }); + + it('should not trigger fireModifierAndEnterKeyPress on Enter without modifier', () => { + textInput = new TextInputInternal({ fireModifierAndEnterKeyPress: mockFireModifierAndEnterKeyPress }); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + const keyEvent = new KeyboardEvent('keydown', { key: 'Enter' }); + + inputElement.dispatchEvent(keyEvent); + + expect(mockFireModifierAndEnterKeyPress).not.toHaveBeenCalled(); + }); + + it('should call checkValidation on blur event', () => { + const checkTextElementValidation = jest.requireMock('../../../helper/validator').checkTextElementValidation; + textInput = new TextInputInternal({}); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + inputElement.dispatchEvent(new Event('blur')); + + expect(checkTextElementValidation).toHaveBeenCalled(); + }); + + it('should call checkValidation on input event', () => { + const checkTextElementValidation = jest.requireMock('../../../helper/validator').checkTextElementValidation; + textInput = new TextInputInternal({}); + document.body.appendChild(textInput.render); + + const inputElement = document.body.querySelector('.mynah-form-input') as HTMLInputElement; + inputElement.dispatchEvent(new Event('input')); + + expect(checkTextElementValidation).toHaveBeenCalled(); + }); + + it('should handle validation patterns', () => { + const validationPatterns = { + operator: 'and' as const, + patterns: [ { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, errorMessage: 'Invalid email' } ] + }; + + textInput = new TextInputInternal({ validationPatterns }); + expect(textInput.render).toBeDefined(); + }); + + it('should handle mandatory field', () => { + textInput = new TextInputInternal({ mandatory: true }); + expect(textInput.render).toBeDefined(); + }); + + it('should handle description element', () => { + const descriptionElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test description' ], + classNames: [ 'test-description' ] + }); + + textInput = new TextInputInternal({ description: descriptionElement }); + document.body.appendChild(textInput.render); + + const description = document.body.querySelector('.test-description'); + expect(description?.textContent).toBe('Test description'); + }); + + it('should have validation error block', () => { + textInput = new TextInputInternal({}); + document.body.appendChild(textInput.render); + + const errorBlock = document.body.querySelector('.mynah-form-input-validation-error-block'); + expect(errorBlock).toBeDefined(); + }); + }); + + describe('TextInput Factory', () => { + it('should create TextInputInternal by default', () => { + const textInputFactory = new TextInput({}); + expect(textInputFactory).toBeInstanceOf(TextInputInternal); + }); + + it('should have abstract methods', () => { + const textInputFactory = new TextInput({}); + expect(typeof textInputFactory.setValue).toBe('function'); + expect(typeof textInputFactory.getValue).toBe('function'); + expect(typeof textInputFactory.setEnabled).toBe('function'); + expect(typeof textInputFactory.checkValidation).toBe('function'); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty props object', () => { + textInput = new TextInputInternal({}); + expect(textInput.render).toBeDefined(); + expect(textInput.getValue()).toBe(''); + }); + + it('should handle null/undefined values gracefully', () => { + const props: TextInputProps = { + label: undefined, + description: undefined, + onChange: undefined, + onKeyPress: undefined, + fireModifierAndEnterKeyPress: undefined + }; + + textInput = new TextInputInternal(props); + expect(textInput.render).toBeDefined(); + }); + + it('should handle numeric value as string', () => { + textInput = new TextInputInternal({ value: '123' }); + expect(textInput.getValue()).toBe('123'); + }); + + it('should handle empty string value', () => { + textInput = new TextInputInternal({ value: '' }); + expect(textInput.getValue()).toBe(''); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/source-link/source-link-body.spec.ts b/vendor/mynah-ui/src/__test__/components/source-link/source-link-body.spec.ts new file mode 100644 index 00000000..147b5e2e --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/source-link/source-link-body.spec.ts @@ -0,0 +1,334 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SourceLinkBody, SourceLinkBodyProps } from '../../../components/source-link/source-link-body'; +import { SourceLink, ReferenceTrackerInformation } from '../../../static'; +import { DomBuilder } from '../../../helper/dom'; + +describe('SourceLinkBody Component', () => { + let sourceLinkBody: SourceLinkBody; + + const basicSuggestion: Partial = { + title: 'Test Source', + url: 'https://example.com', + body: 'This is the body content of the source link' + }; + + const suggestionWithoutBody: Partial = { + title: 'Source without body', + url: 'https://example.com' + }; + + const suggestionWithEmptyBody: Partial = { + title: 'Source with empty body', + url: 'https://example.com', + body: '' + }; + + beforeEach(() => { + document.body.innerHTML = ''; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('Basic Functionality', () => { + it('should create source link body with basic props', () => { + sourceLinkBody = new SourceLinkBody({ suggestion: basicSuggestion }); + + expect(sourceLinkBody.render).toBeDefined(); + expect(sourceLinkBody.render.classList.contains('mynah-card-body')).toBe(true); + }); + + it('should render body content', () => { + sourceLinkBody = new SourceLinkBody({ suggestion: basicSuggestion }); + document.body.appendChild(sourceLinkBody.render); + + expect(sourceLinkBody.render.textContent).toContain(basicSuggestion.body); + }); + + it('should store props reference', () => { + const props: SourceLinkBodyProps = { suggestion: basicSuggestion }; + sourceLinkBody = new SourceLinkBody(props); + + expect(sourceLinkBody.props).toBe(props); + }); + }); + + describe('Body Content Handling', () => { + it('should handle suggestion with body content', () => { + sourceLinkBody = new SourceLinkBody({ suggestion: basicSuggestion }); + document.body.appendChild(sourceLinkBody.render); + + expect(sourceLinkBody.render.textContent).toBe(basicSuggestion.body); + }); + + it('should handle suggestion without body', () => { + sourceLinkBody = new SourceLinkBody({ suggestion: suggestionWithoutBody }); + document.body.appendChild(sourceLinkBody.render); + + // Should render empty content + expect(sourceLinkBody.render.textContent).toBe(''); + }); + + it('should handle suggestion with empty body', () => { + sourceLinkBody = new SourceLinkBody({ suggestion: suggestionWithEmptyBody }); + document.body.appendChild(sourceLinkBody.render); + + expect(sourceLinkBody.render.textContent).toBe(''); + }); + + it('should handle undefined suggestion body', () => { + const suggestionUndefinedBody: Partial = { + title: 'Test', + url: 'https://example.com', + body: undefined + }; + + sourceLinkBody = new SourceLinkBody({ suggestion: suggestionUndefinedBody }); + document.body.appendChild(sourceLinkBody.render); + + expect(sourceLinkBody.render.textContent).toBe(''); + }); + }); + + describe('Children Handling', () => { + it('should handle additional children elements', () => { + const childElement = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'test-child' ], + children: [ 'Child content' ] + }); + + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + children: [ childElement ] + }); + + document.body.appendChild(sourceLinkBody.render); + + const child = document.body.querySelector('.test-child'); + expect(child).toBeDefined(); + expect(child?.textContent).toBe('Child content'); + }); + + it('should handle multiple children', () => { + const child1 = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'child-1' ], + children: [ 'Child 1' ] + }); + + const child2 = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'child-2' ], + children: [ 'Child 2' ] + }); + + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + children: [ child1, child2 ] + }); + + document.body.appendChild(sourceLinkBody.render); + + const firstChild = document.body.querySelector('.child-1'); + const secondChild = document.body.querySelector('.child-2'); + + expect(firstChild?.textContent).toBe('Child 1'); + expect(secondChild?.textContent).toBe('Child 2'); + }); + + it('should handle string children', () => { + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + children: [ 'String child content' ] + }); + + document.body.appendChild(sourceLinkBody.render); + + expect(sourceLinkBody.render.textContent).toContain('String child content'); + }); + + it('should handle HTML element children', () => { + const htmlElement = document.createElement('div'); + htmlElement.textContent = 'HTML element child'; + htmlElement.className = 'html-child'; + + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + children: [ htmlElement ] + }); + + document.body.appendChild(sourceLinkBody.render); + + const child = document.body.querySelector('.html-child'); + expect(child?.textContent).toBe('HTML element child'); + }); + + it('should handle empty children array', () => { + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + children: [] + }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + + it('should handle undefined children', () => { + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + children: undefined + }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + }); + + describe('Highlight Range with Tooltip', () => { + it('should handle highlight range information', () => { + const highlightInfo: ReferenceTrackerInformation[] = [ + { + licenseName: 'MIT', + repository: 'https://github.com/example/repo', + url: 'https://example.com', + recommendationContentSpan: { start: 0, end: 10 }, + information: 'Test information' + } + ]; + + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + highlightRangeWithTooltip: highlightInfo + }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + + it('should handle empty highlight range array', () => { + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + highlightRangeWithTooltip: [] + }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + + it('should handle undefined highlight range', () => { + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + highlightRangeWithTooltip: undefined + }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + + it('should handle multiple highlight ranges', () => { + const highlightInfo: ReferenceTrackerInformation[] = [ + { + licenseName: 'MIT', + repository: 'https://github.com/example/repo1', + url: 'https://example1.com', + recommendationContentSpan: { start: 0, end: 5 }, + information: 'First information' + }, + { + licenseName: 'Apache-2.0', + repository: 'https://github.com/example/repo2', + url: 'https://example2.com', + recommendationContentSpan: { start: 10, end: 15 }, + information: 'Second information' + } + ]; + + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + highlightRangeWithTooltip: highlightInfo + }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty suggestion object', () => { + sourceLinkBody = new SourceLinkBody({ suggestion: {} }); + + expect(sourceLinkBody.render).toBeDefined(); + document.body.appendChild(sourceLinkBody.render); + expect(sourceLinkBody.render.textContent).toBe(''); + }); + + it('should handle null suggestion', () => { + // The component should handle null gracefully by using optional chaining + sourceLinkBody = new SourceLinkBody({ suggestion: {} }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + + it('should handle suggestion with only title', () => { + const titleOnlySuggestion: Partial = { + title: 'Only title' + }; + + sourceLinkBody = new SourceLinkBody({ suggestion: titleOnlySuggestion }); + document.body.appendChild(sourceLinkBody.render); + + expect(sourceLinkBody.render.textContent).toBe(''); + }); + + it('should handle complex body content', () => { + const complexBodySuggestion: Partial = { + title: 'Complex body', + url: 'https://example.com', + body: 'This is a complex body with multiple lines and special characters' + }; + + sourceLinkBody = new SourceLinkBody({ suggestion: complexBodySuggestion }); + document.body.appendChild(sourceLinkBody.render); + + // Check that the content is rendered (whitespace may be normalized) + expect(sourceLinkBody.render.textContent).toContain('This is a complex body'); + expect(sourceLinkBody.render.textContent).toContain('multiple lines'); + expect(sourceLinkBody.render.textContent).toContain('special characters'); + }); + }); + + describe('Component Integration', () => { + it('should integrate properly with CardBody component', () => { + sourceLinkBody = new SourceLinkBody({ suggestion: basicSuggestion }); + document.body.appendChild(sourceLinkBody.render); + + // Should have card body class from CardBody component + expect(sourceLinkBody.render.classList.contains('mynah-card-body')).toBe(true); + }); + + it('should pass all props to CardBody correctly', () => { + const childElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ 'Test child' ] + }); + + const highlightInfo: ReferenceTrackerInformation[] = [ + { + licenseName: 'MIT', + repository: 'https://github.com/test/repo', + url: 'https://test.com', + recommendationContentSpan: { start: 0, end: 5 }, + information: 'Test information' + } + ]; + + sourceLinkBody = new SourceLinkBody({ + suggestion: basicSuggestion, + children: [ childElement ], + highlightRangeWithTooltip: highlightInfo + }); + + expect(sourceLinkBody.render).toBeDefined(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/source-link/source-link-header.spec.ts b/vendor/mynah-ui/src/__test__/components/source-link/source-link-header.spec.ts new file mode 100644 index 00000000..38dc2033 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/source-link/source-link-header.spec.ts @@ -0,0 +1,671 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SourceLinkHeader } from '../../../components/source-link/source-link-header'; +import { SourceLink, MynahEventNames } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; + +// Mock the global events +jest.mock('../../../helper/events', () => ({ + MynahUIGlobalEvents: { + getInstance: jest.fn(() => ({ + addListener: jest.fn() + })) + } +})); + +// Mock the overlay component +jest.mock('../../../components/overlay', () => ({ + Overlay: jest.fn().mockImplementation(() => ({ + close: jest.fn() + })), + OverlayHorizontalDirection: { + START_TO_RIGHT: 'start-to-right' + }, + OverlayVerticalDirection: { + TO_TOP: 'to-top' + } +})); + +describe('SourceLinkHeader Component', () => { + let sourceLinkHeader: SourceLinkHeader; + let mockOnClick: jest.Mock; + + const basicSourceLink: SourceLink = { + title: 'Test Source Link', + url: 'https://example.com/test/path' + }; + + const sourceWithBody: SourceLink = { + title: 'Source with Body', + url: 'https://github.com/user/repo/blob/main/file.js', + body: 'This source has body content for preview' + }; + + const sourceWithMetadata: SourceLink = { + title: 'GitHub Repository', + url: 'https://github.com/example/awesome-repo', + metadata: { + github: { + stars: 1500, + forks: 250, + isOfficialDoc: true, + lastActivityDate: Date.now() - 86400000, // 1 day ago + score: 95 + } + } + }; + + const sourceWithComplexMetadata: SourceLink = { + title: 'Stack Overflow Question', + url: 'https://stackoverflow.com/questions/12345/test-question', + metadata: { + stackoverflow: { + answerCount: 8, + isAccepted: true, + score: 42, + lastActivityDate: Date.now() - 3600000 // 1 hour ago + }, + github: { + stars: 500, + forks: 75 + } + } + }; + + beforeEach(() => { + document.body.innerHTML = ''; + mockOnClick = jest.fn(); + jest.clearAllMocks(); + jest.clearAllTimers(); + jest.useFakeTimers(); + }); + + afterEach(() => { + document.body.innerHTML = ''; + jest.runOnlyPendingTimers(); + jest.useRealTimers(); + }); + + describe('Basic Functionality', () => { + it('should create source link header with basic props', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + + expect(sourceLinkHeader.render).toBeDefined(); + expect(sourceLinkHeader.render.classList.contains('mynah-source-link-header')).toBe(true); + }); + + it('should render source link title', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const title = document.body.querySelector('.mynah-source-link-title'); + expect(title?.textContent).toContain(basicSourceLink.title); + }); + + it('should render source link URL with correct href', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const titleLink = document.body.querySelector('.mynah-source-link-title'); + const urlLink = document.body.querySelector('.mynah-source-link-url'); + + expect(titleLink?.getAttribute('href')).toBe(basicSourceLink.url); + expect(urlLink?.getAttribute('href')).toBe(basicSourceLink.url); + }); + + it('should have target="_blank" for external links', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const titleLink = document.body.querySelector('.mynah-source-link-title'); + const urlLink = document.body.querySelector('.mynah-source-link-url'); + + expect(titleLink?.getAttribute('target')).toBe('_blank'); + expect(urlLink?.getAttribute('target')).toBe('_blank'); + }); + + it('should render external link icon', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const expandIcon = document.body.querySelector('.mynah-source-link-expand-icon'); + expect(expandIcon).toBeDefined(); + }); + + it('should have correct test IDs', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const wrapper = document.body.querySelector('[data-testid*="link-wrapper"]'); + const link = document.body.querySelector('[data-testid*="link"]'); + + expect(wrapper).toBeDefined(); + expect(link).toBeDefined(); + }); + }); + + describe('URL Processing', () => { + it('should process URL correctly by removing protocol', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + expect(urlElement?.innerHTML).toContain('example.com'); + expect(urlElement?.innerHTML).toContain('test'); + expect(urlElement?.innerHTML).toContain('path'); + }); + + it('should handle HTTPS URLs', () => { + const httpsSource: SourceLink = { + title: 'HTTPS Source', + url: 'https://secure.example.com/path' + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: httpsSource }); + document.body.appendChild(sourceLinkHeader.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + expect(urlElement?.innerHTML).toContain('secure.example.com'); + expect(urlElement?.innerHTML).not.toContain('https://'); + }); + + it('should handle HTTP URLs', () => { + const httpSource: SourceLink = { + title: 'HTTP Source', + url: 'http://example.com/path' + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: httpSource }); + document.body.appendChild(sourceLinkHeader.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + expect(urlElement?.innerHTML).toContain('example.com'); + expect(urlElement?.innerHTML).not.toContain('http://'); + }); + + it('should handle URLs with trailing slash', () => { + const trailingSlashSource: SourceLink = { + title: 'Trailing Slash', + url: 'https://example.com/path/' + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: trailingSlashSource }); + document.body.appendChild(sourceLinkHeader.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + expect(urlElement?.innerHTML).toContain('example.com'); + expect(urlElement?.innerHTML).toContain('path'); + }); + + it('should wrap URL parts in spans', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + const spans = urlElement?.querySelectorAll('span'); + + expect(spans?.length).toBeGreaterThan(0); + }); + + it('should set origin attribute', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const header = document.body.querySelector('.mynah-source-link-header'); + expect(header?.getAttribute('origin')).toBeDefined(); + }); + }); + + describe('Click Handling', () => { + it('should handle onClick for title link', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: basicSourceLink, + onClick: mockOnClick + }); + document.body.appendChild(sourceLinkHeader.render); + + const titleLink = document.body.querySelector('.mynah-source-link-title') as HTMLElement; + titleLink.click(); + + expect(mockOnClick).toHaveBeenCalled(); + }); + + it('should handle onClick for URL link', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: basicSourceLink, + onClick: mockOnClick + }); + document.body.appendChild(sourceLinkHeader.render); + + const urlLink = document.body.querySelector('.mynah-source-link-url') as HTMLElement; + urlLink.click(); + + expect(mockOnClick).toHaveBeenCalled(); + }); + + it('should handle auxclick (middle click) for title link', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: basicSourceLink, + onClick: mockOnClick + }); + document.body.appendChild(sourceLinkHeader.render); + + const titleLink = document.body.querySelector('.mynah-source-link-title') as HTMLElement; + const auxClickEvent = new MouseEvent('auxclick', { button: 1 }); + titleLink.dispatchEvent(auxClickEvent); + + expect(mockOnClick).toHaveBeenCalled(); + }); + + it('should handle auxclick for URL link', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: basicSourceLink, + onClick: mockOnClick + }); + document.body.appendChild(sourceLinkHeader.render); + + const urlLink = document.body.querySelector('.mynah-source-link-url') as HTMLElement; + const auxClickEvent = new MouseEvent('auxclick', { button: 1 }); + urlLink.dispatchEvent(auxClickEvent); + + expect(mockOnClick).toHaveBeenCalled(); + }); + + it('should not add click handlers when onClick is not provided', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const titleLink = document.body.querySelector('.mynah-source-link-title') as HTMLElement; + titleLink.click(); + + // Should not throw error + expect(sourceLinkHeader.render).toBeDefined(); + }); + }); + + describe('Metadata Rendering', () => { + it('should render metadata when provided', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaBlock = document.body.querySelector('.mynah-title-meta-block'); + expect(metaBlock).toBeDefined(); + }); + + it('should not render metadata when not provided', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const metaBlock = document.body.querySelector('.mynah-title-meta-block'); + expect(metaBlock).toBeNull(); + }); + + it('should render accepted answer icon', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithComplexMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const approvedAnswer = document.body.querySelector('.approved-answer'); + expect(approvedAnswer).toBeDefined(); + }); + + it('should render stars count', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaItems = document.body.querySelectorAll('.mynah-title-meta-block-item-text'); + const starsItem = Array.from(metaItems).find(item => + item.textContent?.includes('contributors') + ); + expect(starsItem).toBeDefined(); + expect(starsItem?.textContent).toContain('1500'); + }); + + it('should render forks count', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaItems = document.body.querySelectorAll('.mynah-title-meta-block-item-text'); + const forksItem = Array.from(metaItems).find(item => + item.textContent?.includes('forks') + ); + expect(forksItem).toBeDefined(); + expect(forksItem?.textContent).toContain('250'); + }); + + it('should render answer count', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithComplexMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaItems = document.body.querySelectorAll('.mynah-title-meta-block-item-text'); + const answerItem = Array.from(metaItems).find(item => + item.textContent === '8' + ); + expect(answerItem).toBeDefined(); + }); + + it('should render score', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithComplexMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaItems = document.body.querySelectorAll('.mynah-title-meta-block-item-text'); + const scoreItem = Array.from(metaItems).find(item => + item.textContent === '42' + ); + expect(scoreItem).toBeDefined(); + }); + + it('should render last activity date', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaItems = document.body.querySelectorAll('.mynah-title-meta-block-item'); + // Check that we have meta items (the date formatting might vary) + expect(metaItems.length).toBeGreaterThan(0); + + // Check for calendar icon which indicates date item + const calendarIcon = document.body.querySelector('.mynah-title-meta-block-item .mynah-icon'); + expect(calendarIcon).toBeDefined(); + }); + + it('should handle multiple metadata sources', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithComplexMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaBlock = document.body.querySelector('.mynah-title-meta-block'); + const metaItems = metaBlock?.querySelectorAll('.mynah-title-meta-block-item'); + + expect(metaItems?.length).toBeGreaterThan(1); + }); + + it('should handle empty metadata object', () => { + const sourceWithEmptyMetadata: SourceLink = { + title: 'Empty Metadata', + url: 'https://example.com', + metadata: {} + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: sourceWithEmptyMetadata }); + document.body.appendChild(sourceLinkHeader.render); + + const metaBlock = document.body.querySelector('.mynah-title-meta-block'); + expect(metaBlock).toBeDefined(); + + const metaItems = metaBlock?.querySelectorAll('.mynah-title-meta-block-item'); + expect(metaItems?.length).toBe(0); + }); + }); + + describe('Preview Functionality', () => { + it('should show preview on hover when showCardOnHover is true', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: sourceWithBody, + showCardOnHover: true + }); + document.body.appendChild(sourceLinkHeader.render); + + const header = document.body.querySelector('.mynah-source-link-header') as HTMLElement; + + // Trigger mouseenter + const mouseEnterEvent = new MouseEvent('mouseenter'); + header.dispatchEvent(mouseEnterEvent); + + // Fast-forward timer + jest.advanceTimersByTime(500); + + // Overlay should be created (mocked) + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should hide preview on mouseleave', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: sourceWithBody, + showCardOnHover: true + }); + document.body.appendChild(sourceLinkHeader.render); + + const header = document.body.querySelector('.mynah-source-link-header') as HTMLElement; + + // Trigger mouseenter then mouseleave + header.dispatchEvent(new MouseEvent('mouseenter')); + header.dispatchEvent(new MouseEvent('mouseleave')); + + // Timer should be cleared + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).not.toHaveBeenCalled(); + }); + + it('should show preview on focus', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: sourceWithBody, + showCardOnHover: true + }); + document.body.appendChild(sourceLinkHeader.render); + + const header = document.body.querySelector('.mynah-source-link-header') as HTMLElement; + + // Trigger focus + const focusEvent = new FocusEvent('focus'); + header.dispatchEvent(focusEvent); + + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).toHaveBeenCalled(); + }); + + it('should hide preview on blur', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: sourceWithBody, + showCardOnHover: true + }); + document.body.appendChild(sourceLinkHeader.render); + + const header = document.body.querySelector('.mynah-source-link-header') as HTMLElement; + + // Trigger focus then blur + header.dispatchEvent(new FocusEvent('focus')); + header.dispatchEvent(new FocusEvent('blur')); + + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).not.toHaveBeenCalled(); + }); + + it('should not show preview when source has no body', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: basicSourceLink, + showCardOnHover: true + }); + document.body.appendChild(sourceLinkHeader.render); + + const header = document.body.querySelector('.mynah-source-link-header') as HTMLElement; + header.dispatchEvent(new MouseEvent('mouseenter')); + + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).not.toHaveBeenCalled(); + }); + + it('should not add hover events when showCardOnHover is false', () => { + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: sourceWithBody, + showCardOnHover: false + }); + document.body.appendChild(sourceLinkHeader.render); + + const header = document.body.querySelector('.mynah-source-link-header') as HTMLElement; + header.dispatchEvent(new MouseEvent('mouseenter')); + + jest.advanceTimersByTime(500); + + const { Overlay } = jest.requireMock('../../../components/overlay'); + expect(Overlay).not.toHaveBeenCalled(); + }); + }); + + describe('Global Events', () => { + it('should register for root focus events', () => { + const mockAddListener = jest.fn(); + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue({ + addListener: mockAddListener + }); + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + + expect(mockAddListener).toHaveBeenCalledWith( + MynahEventNames.ROOT_FOCUS, + expect.any(Function) + ); + }); + + it('should hide preview on root focus loss', () => { + const mockAddListener = jest.fn(); + let focusCallback: ((data: {focusState: boolean}) => void) | undefined; + + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue({ + addListener: (event: string, callback: any) => { + if (event === MynahEventNames.ROOT_FOCUS) { + focusCallback = callback; + } + mockAddListener(event, callback); + } + }); + + sourceLinkHeader = new SourceLinkHeader({ + sourceLink: sourceWithBody, + showCardOnHover: true + }); + + // Simulate showing preview + document.body.appendChild(sourceLinkHeader.render); + const header = document.body.querySelector('.mynah-source-link-header') as HTMLElement; + header.dispatchEvent(new MouseEvent('mouseenter')); + jest.advanceTimersByTime(500); + + // Simulate root focus loss + if (focusCallback != null) { + focusCallback({ focusState: false }); + } + + // Should hide preview (tested through mock) + expect(mockAddListener).toHaveBeenCalled(); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty URL', () => { + const emptyUrlSource: SourceLink = { + title: 'Empty URL', + url: '' + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: emptyUrlSource }); + expect(sourceLinkHeader.render).toBeDefined(); + }); + + it('should handle URL without protocol', () => { + const noProtocolSource: SourceLink = { + title: 'No Protocol', + url: 'example.com/path' + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: noProtocolSource }); + document.body.appendChild(sourceLinkHeader.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + expect(urlElement?.innerHTML).toContain('example.com'); + }); + + it('should handle very long URLs', () => { + const longUrlSource: SourceLink = { + title: 'Long URL', + url: 'https://example.com/very/long/path/with/many/segments/that/goes/on/and/on' + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: longUrlSource }); + expect(sourceLinkHeader.render).toBeDefined(); + }); + + it('should handle special characters in URL', () => { + const specialCharSource: SourceLink = { + title: 'Special Chars', + url: 'https://example.com/path?query=test¶m=value#section' + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: specialCharSource }); + document.body.appendChild(sourceLinkHeader.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + expect(urlElement?.innerHTML).toContain('example.com'); + }); + + it('should handle metadata with undefined values', () => { + const undefinedMetadataSource: SourceLink = { + title: 'Undefined Metadata', + url: 'https://example.com', + metadata: { + test: { + stars: undefined, + forks: undefined, + answerCount: undefined, + isAccepted: undefined, + score: undefined, + lastActivityDate: undefined + } + } + }; + + sourceLinkHeader = new SourceLinkHeader({ sourceLink: undefinedMetadataSource }); + document.body.appendChild(sourceLinkHeader.render); + + const metaBlock = document.body.querySelector('.mynah-title-meta-block'); + expect(metaBlock).toBeDefined(); + }); + }); + + describe('Component Structure', () => { + it('should have proper DOM structure', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + // Should have main header + const header = document.body.querySelector('.mynah-source-link-header'); + expect(header).toBeDefined(); + + // Should have thumbnail + const thumbnail = header?.querySelector('.mynah-source-thumbnail'); + expect(thumbnail).toBeDefined(); + + // Should have title wrapper + const titleWrapper = header?.querySelector('.mynah-source-link-title-wrapper'); + expect(titleWrapper).toBeDefined(); + + // Should have title and URL links + const title = titleWrapper?.querySelector('.mynah-source-link-title'); + const url = titleWrapper?.querySelector('.mynah-source-link-url'); + expect(title).toBeDefined(); + expect(url).toBeDefined(); + }); + + it('should maintain proper accessibility attributes', () => { + sourceLinkHeader = new SourceLinkHeader({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkHeader.render); + + const titleLink = document.body.querySelector('.mynah-source-link-title'); + const urlLink = document.body.querySelector('.mynah-source-link-url'); + + expect(titleLink?.getAttribute('href')).toBeTruthy(); + expect(titleLink?.getAttribute('target')).toBe('_blank'); + expect(urlLink?.getAttribute('href')).toBeTruthy(); + expect(urlLink?.getAttribute('target')).toBe('_blank'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/components/source-link/source-link.spec.ts b/vendor/mynah-ui/src/__test__/components/source-link/source-link.spec.ts new file mode 100644 index 00000000..ab368e05 --- /dev/null +++ b/vendor/mynah-ui/src/__test__/components/source-link/source-link.spec.ts @@ -0,0 +1,265 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { SourceLinkCard } from '../../../components/source-link/source-link'; +import { SourceLink } from '../../../static'; + +describe('SourceLinkCard Component', () => { + let sourceLinkCard: SourceLinkCard; + + const basicSourceLink: SourceLink = { + title: 'Test Source Link', + url: 'https://example.com/test', + id: 'test-id' + }; + + const sourceWithBody: SourceLink = { + title: 'Source with Body', + url: 'https://example.com/with-body', + body: 'This is the body content of the source link', + type: 'documentation' + }; + + const sourceWithMetadata: SourceLink = { + title: 'Source with Metadata', + url: 'https://github.com/example/repo', + metadata: { + github: { + stars: 150, + forks: 25, + isOfficialDoc: true, + lastActivityDate: Date.now() - 86400000 // 1 day ago + } + } + }; + + beforeEach(() => { + document.body.innerHTML = ''; + }); + + afterEach(() => { + document.body.innerHTML = ''; + }); + + describe('Basic Functionality', () => { + it('should create source link card with basic props', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + + expect(sourceLinkCard.render).toBeDefined(); + expect(sourceLinkCard.render.classList.contains('mynah-card')).toBe(true); + }); + + it('should render source link header', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkCard.render); + + const header = document.body.querySelector('.mynah-source-link-header'); + expect(header).toBeDefined(); + }); + + it('should render source link title', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkCard.render); + + const title = document.body.querySelector('.mynah-source-link-title'); + expect(title?.textContent).toContain(basicSourceLink.title); + }); + + it('should render source link URL', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkCard.render); + + const urlElement = document.body.querySelector('.mynah-source-link-url'); + expect(urlElement?.getAttribute('href')).toBe(basicSourceLink.url); + }); + + it('should have correct test ID', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkCard.render); + + const cardElement = document.body.querySelector('[data-testid*="link-preview-overlay-card"]'); + expect(cardElement).toBeDefined(); + }); + }); + + describe('Source Link with Body', () => { + it('should render body when provided', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: sourceWithBody }); + document.body.appendChild(sourceLinkCard.render); + + const cardBody = document.body.querySelector('.mynah-card-body'); + expect(cardBody).toBeDefined(); + }); + + it('should not render body when not provided', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkCard.render); + + const cardBody = document.body.querySelector('.mynah-card-body'); + expect(cardBody).toBeNull(); + }); + + it('should display body content correctly', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: sourceWithBody }); + document.body.appendChild(sourceLinkCard.render); + + const cardBody = document.body.querySelector('.mynah-card-body'); + expect(cardBody?.textContent).toContain(sourceWithBody.body); + }); + }); + + describe('Compact Mode', () => { + it('should handle compact flat mode', () => { + sourceLinkCard = new SourceLinkCard({ + sourceLink: basicSourceLink, + compact: 'flat' + }); + + expect(sourceLinkCard.render).toBeDefined(); + }); + + it('should handle compact true mode', () => { + sourceLinkCard = new SourceLinkCard({ + sourceLink: basicSourceLink, + compact: true + }); + + expect(sourceLinkCard.render).toBeDefined(); + }); + }); + + describe('Card Properties', () => { + it('should create card with no border', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkCard.render); + + // Card should not have border class + const card = document.body.querySelector('.mynah-card'); + expect(card?.classList.contains('mynah-card-border')).toBe(false); + }); + + it('should create card with no background', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + document.body.appendChild(sourceLinkCard.render); + + // Card should not have background class + const card = document.body.querySelector('.mynah-card'); + expect(card?.classList.contains('mynah-card-background')).toBe(false); + }); + }); + + describe('Complex Source Links', () => { + it('should handle source link with metadata', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: sourceWithMetadata }); + document.body.appendChild(sourceLinkCard.render); + + const header = document.body.querySelector('.mynah-source-link-header'); + expect(header).toBeDefined(); + }); + + it('should handle source link with all properties', () => { + const complexSource: SourceLink = { + title: 'Complex Source', + url: 'https://example.com/complex', + body: 'Complex body content', + type: 'api-doc', + id: 'complex-id', + metadata: { + stackoverflow: { + score: 42, + answerCount: 5, + isAccepted: true, + lastActivityDate: Date.now() - 3600000 // 1 hour ago + } + } + }; + + sourceLinkCard = new SourceLinkCard({ sourceLink: complexSource }); + document.body.appendChild(sourceLinkCard.render); + + expect(sourceLinkCard.render).toBeDefined(); + + const title = document.body.querySelector('.mynah-source-link-title'); + expect(title?.textContent).toContain(complexSource.title); + + const cardBody = document.body.querySelector('.mynah-card-body'); + expect(cardBody).toBeDefined(); + }); + }); + + describe('Edge Cases', () => { + it('should handle empty source link', () => { + const emptySource: SourceLink = { + title: '', + url: '' + }; + + sourceLinkCard = new SourceLinkCard({ sourceLink: emptySource }); + expect(sourceLinkCard.render).toBeDefined(); + }); + + it('should handle source link with undefined body', () => { + const sourceWithUndefinedBody: SourceLink = { + title: 'Test', + url: 'https://example.com', + body: undefined + }; + + sourceLinkCard = new SourceLinkCard({ sourceLink: sourceWithUndefinedBody }); + document.body.appendChild(sourceLinkCard.render); + + const cardBody = document.body.querySelector('.mynah-card-body'); + expect(cardBody).toBeNull(); + }); + + it('should handle source link with empty metadata', () => { + const sourceWithEmptyMetadata: SourceLink = { + title: 'Test', + url: 'https://example.com', + metadata: {} + }; + + sourceLinkCard = new SourceLinkCard({ sourceLink: sourceWithEmptyMetadata }); + expect(sourceLinkCard.render).toBeDefined(); + }); + + it('should handle source link with null metadata', () => { + const sourceWithNullMetadata: SourceLink = { + title: 'Test', + url: 'https://example.com', + metadata: undefined + }; + + sourceLinkCard = new SourceLinkCard({ sourceLink: sourceWithNullMetadata }); + expect(sourceLinkCard.render).toBeDefined(); + }); + }); + + describe('Component Structure', () => { + it('should have proper component hierarchy', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: sourceWithBody }); + document.body.appendChild(sourceLinkCard.render); + + // Should have card wrapper + const card = document.body.querySelector('.mynah-card'); + expect(card).toBeDefined(); + + // Should have header + const header = card?.querySelector('.mynah-source-link-header'); + expect(header).toBeDefined(); + + // Should have body + const body = card?.querySelector('.mynah-card-body'); + expect(body).toBeDefined(); + }); + + it('should maintain source link reference', () => { + sourceLinkCard = new SourceLinkCard({ sourceLink: basicSourceLink }); + + // The component should store the source link reference + expect((sourceLinkCard as any).sourceLink).toBe(basicSourceLink); + }); + }); +}); diff --git a/vendor/mynah-ui/src/__test__/main.spec.ts b/vendor/mynah-ui/src/__test__/main.spec.ts new file mode 100644 index 00000000..f7a8b04f --- /dev/null +++ b/vendor/mynah-ui/src/__test__/main.spec.ts @@ -0,0 +1,99 @@ +import { ChatItemType, MynahUI, MynahUIDataModel } from '../main'; + +const testTabId = 'tab-1'; +let testMynahUI: MynahUI; +afterEach(() => { + // Clear the dom and store between tests + document.body.innerHTML = ''; + testMynahUI.updateStore(testTabId, { + loadingChat: false, + chatItems: [], + }); +}); + +describe('mynah-ui', () => { + it('render chat items', () => { + const testTabId = 'tab-1'; + testMynahUI = new MynahUI({ + tabs: { + [testTabId]: { + isSelected: true, + store: { + loadingChat: false, + } + } + } + }); + + testMynahUI.addChatItem(testTabId, { type: ChatItemType.PROMPT, body: 'What is a react hook' }); + testMynahUI.addChatItem(testTabId, { type: ChatItemType.ANSWER_STREAM }); + testMynahUI.updateLastChatAnswer(testTabId, { body: 'Just a function.' }); + testMynahUI.addChatItem(testTabId, { + type: ChatItemType.ANSWER, + followUp: { + text: 'Suggested follow up', + options: [ + { + pillText: 'Follow up one', + prompt: 'Follow up one', + }, + ], + }, + }); + const cardElements = document.body.querySelectorAll('.mynah-chat-item-card'); + expect(cardElements).toHaveLength(3); + expect(cardElements[0].textContent).toBe('What is a react hook'); + expect(cardElements[1].textContent).toContain('Just a function.'); + expect(cardElements[2].textContent).toContain('Suggested follow up'); + expect(cardElements[2].textContent).toContain('Follow up one'); + }); + + it('loading state', () => { + const testMynahUI = new MynahUI({ + tabs: { + [testTabId]: { + isSelected: true, + store: { + loadingChat: false, + } + } + } + }); + + testMynahUI.addChatItem(testTabId, { type: ChatItemType.PROMPT, body: 'What is python' }); + // Still generating an answer + testMynahUI.addChatItem(testTabId, { type: ChatItemType.ANSWER_STREAM }); + testMynahUI.updateStore(testTabId, { + loadingChat: true, + }); + + const cardElements = document.body.querySelectorAll('.mynah-chat-item-card'); + expect(cardElements).toHaveLength(2); + + expect(cardElements[0].textContent).toBe('What is python'); + expect(cardElements[1].textContent).toBe('Amazon Q is generating your answer...'); + }); + + it('does not break on data store extension', () => { + const testMynahUI = new MynahUI({ + tabs: { + [testTabId]: { + isSelected: true, + store: { + loadingChat: false, + } + } + } + }); + + type ExtendedDataModel = MynahUIDataModel & { someOtherProperty: boolean }; + const props: ExtendedDataModel = { someOtherProperty: true }; + + try { + testMynahUI.updateStore(testTabId, props); + } catch (e) { + console.log(e); + expect(true).toBe(false); + } + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/button.spec.ts b/vendor/mynah-ui/src/components/__test__/button.spec.ts new file mode 100644 index 00000000..ce9cedf3 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/button.spec.ts @@ -0,0 +1,82 @@ +import { Button } from '../button'; + +describe('button', () => { + it('label', () => { + const mockOnClickHandler = jest.fn(); + const testButton = new Button({ + label: 'Test button', + onClick: mockOnClickHandler, + }); + + expect(testButton.render).toBeDefined(); + expect(testButton.render.querySelector('span')?.textContent).toBe('Test button'); + + testButton.updateLabel('Updated label'); + expect(testButton.render.textContent).toBe('Updated label'); + }); + + it('attributes', () => { + const mockOnClickHandler = jest.fn(); + const testButton = new Button({ + label: 'Test button', + attributes: { + id: 'test-id', + }, + onClick: mockOnClickHandler, + }); + + expect(testButton.render.id).toBe('test-id'); + }); + + it('primary style', () => { + const mockOnClickHandler = jest.fn(); + const testButton = new Button({ + label: 'Test button', + primary: false, + onClick: mockOnClickHandler, + }); + const testButton2 = new Button({ + label: 'Test button', + primary: true, + onClick: mockOnClickHandler, + }); + + expect(testButton.render.classList.contains('mynah-button-secondary')).toBeTruthy(); + expect(testButton2.render.classList.contains('mynah-button-secondary')).toBeFalsy(); + }); + + it('enabled', () => { + const mockOnClickHandler = jest.fn(); + const testButton = new Button({ + label: 'Test button', + onClick: mockOnClickHandler, + }); + + expect(testButton.render.disabled).toBeFalsy(); + testButton.setEnabled(false); + expect(testButton.render.disabled).toBeTruthy(); + }); + + it('event handlers', () => { + const mockOnClickHandler = jest.fn(); + const mockMouseOverHandler = jest.fn(); + const testButton = new Button({ + label: 'Test button', + attributes: { + id: 'test-id', + }, + onClick: mockOnClickHandler, + additionalEvents: { + mouseenter: mockMouseOverHandler, + } + }); + + document.body.appendChild(testButton.render); + const testButtonElement = document.querySelector('#test-id') as HTMLElement; + testButtonElement?.click(); + expect(mockOnClickHandler).toHaveBeenCalledTimes(1); + + testButtonElement.dispatchEvent(new Event('mouseenter')); + expect(mockMouseOverHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-buttons.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-buttons.spec.ts new file mode 100644 index 00000000..58d7d5c2 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-buttons.spec.ts @@ -0,0 +1,46 @@ +import { ChatItemButtonsWrapper } from '../../chat-item/chat-item-buttons'; +import { ChatItemButton } from '../../../static'; + +describe('ChatItemButtonsWrapper', () => { + it('should render empty wrapper when no buttons provided', () => { + const buttonsWrapper = new ChatItemButtonsWrapper({ + buttons: null + }); + + expect(buttonsWrapper.render).toBeDefined(); + expect(buttonsWrapper.render.classList.contains('mynah-chat-item-buttons-container')).toBe(true); + }); + + it('should render with buttons array', () => { + const buttons: ChatItemButton[] = [ + { + id: 'test-button', + text: 'Test Button', + description: 'Test button description' + } + ]; + + const buttonsWrapper = new ChatItemButtonsWrapper({ + buttons, + tabId: 'test-tab' + }); + + expect(buttonsWrapper.render).toBeDefined(); + expect(buttonsWrapper.render.classList.contains('mynah-chat-item-buttons-container')).toBe(true); + }); + + it('should apply custom class names', () => { + const customClasses = [ 'custom-class-1', 'custom-class-2' ]; + + const buttonsWrapper = new ChatItemButtonsWrapper({ + buttons: [], + tabId: 'test-tab', + classNames: customClasses + }); + + expect(buttonsWrapper.render).toBeDefined(); + customClasses.forEach(className => { + expect(buttonsWrapper.render.classList.contains(className)).toBe(true); + }); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-card-content.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-card-content.spec.ts new file mode 100644 index 00000000..61535aa3 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-card-content.spec.ts @@ -0,0 +1,107 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItemCardContent, ChatItemCardContentProps } from '../../chat-item/chat-item-card-content'; +import { Config } from '../../../helper/config'; + +// Mock Config +jest.mock('../../../helper/config', () => ({ + Config: { + getInstance: jest.fn() + } +})); + +describe('ChatItemCardContent Animation Speed', () => { + const mockGetInstance = Config.getInstance as jest.MockedFunction; + + beforeEach(() => { + jest.clearAllMocks(); + + mockGetInstance.mockReturnValue({ + // @ts-expect-error + config: { + typewriterStackTime: 100, + typewriterMaxWordTime: 20, + disableTypewriterAnimation: false, + } + }); + + document.body.innerHTML = '
'; + }); + + describe('Animation Configuration', () => { + it('should use fast animation settings', () => { + mockGetInstance.mockReturnValue({ + // @ts-expect-error + config: { + typewriterStackTime: 100, + typewriterMaxWordTime: 20, + disableTypewriterAnimation: false, + } + }); + + const props: ChatItemCardContentProps = { + body: 'Test content', + renderAsStream: true, + }; + + const cardContent = new ChatItemCardContent(props); + expect(mockGetInstance).toHaveBeenCalled(); + expect(cardContent).toBeDefined(); + }); + + it('should disable animation when configured', () => { + mockGetInstance.mockReturnValue({ + // @ts-expect-error + config: { + disableTypewriterAnimation: true, + } + }); + + const props: ChatItemCardContentProps = { + body: 'Test content', + renderAsStream: true, + }; + + const cardContent = new ChatItemCardContent(props); + expect(mockGetInstance).toHaveBeenCalled(); + expect(cardContent).toBeDefined(); + }); + }); + + describe('Stream Ending', () => { + it('should end stream and call animation state change', () => { + const onAnimationStateChange = jest.fn(); + const props: ChatItemCardContentProps = { + body: 'Test content', + renderAsStream: true, + onAnimationStateChange, + }; + + const cardContent = new ChatItemCardContent(props); + cardContent.endStream(); + + expect(onAnimationStateChange).toHaveBeenCalledWith(false); + }); + }); + + describe('Default Values', () => { + it('should use defaults when config is empty', () => { + mockGetInstance.mockReturnValue({ + // @ts-expect-error + config: {} + }); + + const props: ChatItemCardContentProps = { + body: 'Test content', + renderAsStream: true, + }; + + const cardContent = new ChatItemCardContent(props); + expect(mockGetInstance).toHaveBeenCalled(); + expect(cardContent).toBeDefined(); + }); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-card.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-card.spec.ts new file mode 100644 index 00000000..189183c6 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-card.spec.ts @@ -0,0 +1,244 @@ +import { ChatItemCard } from '../../chat-item/chat-item-card'; +import { ChatItemType } from '../../../static'; +import { MynahUIGlobalEvents } from '../../../helper/events'; + +// Mock the tabs store +jest.mock('../../../helper/tabs-store', () => ({ + MynahUITabsStore: { + getInstance: jest.fn(() => ({ + getTabDataStore: jest.fn(() => ({ + subscribe: jest.fn(), + getValue: jest.fn(() => ({})) + })) + })) + } +})); + +// Mock global events +jest.mock('../../../helper/events', () => ({ + MynahUIGlobalEvents: { + getInstance: jest.fn(() => ({ + dispatch: jest.fn() + })) + } +})); + +describe('ChatItemCard', () => { + let mockDispatch: jest.Mock; + + beforeEach(() => { + mockDispatch = jest.fn(); + (MynahUIGlobalEvents.getInstance as jest.Mock).mockReturnValue({ + dispatch: mockDispatch + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should render basic chat item card', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + body: 'Test content' + } + }); + + expect(card.render).toBeDefined(); + }); + + describe('Pills functionality', () => { + it('should render pills when renderAsPills is true', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + icon: 'progress', + body: 'Reading', + fileList: { + filePaths: [ 'file1.ts', 'file2.ts' ], + renderAsPills: true + } + } + } + }); + + const pillElements = card.render.querySelectorAll('.mynah-chat-item-tree-file-pill'); + expect(pillElements.length).toBe(2); + }); + + it('should include icon in customRenderer when pills are enabled', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + icon: 'eye', + body: 'Files read', + fileList: { + filePaths: [ 'test.json' ], + renderAsPills: true + } + } + } + }); + + const iconElement = card.render.querySelector('.mynah-ui-icon-eye'); + expect(iconElement).toBeTruthy(); + }); + + it('should render pills with correct file names', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + body: 'Processing', + fileList: { + filePaths: [ 'package.json', 'tsconfig.json' ], + details: { + 'package.json': { + visibleName: 'package' + }, + 'tsconfig.json': { + visibleName: 'tsconfig' + } + }, + renderAsPills: true + } + } + } + }); + + const pillElements = card.render.querySelectorAll('.mynah-chat-item-tree-file-pill'); + expect(pillElements[0].textContent).toBe('package'); + expect(pillElements[1].textContent).toBe('tsconfig'); + }); + + it('should apply deleted styling to deleted files', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + body: 'Changes', + fileList: { + filePaths: [ 'deleted.ts', 'normal.ts' ], + deletedFiles: [ 'deleted.ts' ], + renderAsPills: true + } + } + } + }); + + const deletedPill = card.render.querySelector('.mynah-chat-item-tree-file-pill-deleted'); + expect(deletedPill).toBeTruthy(); + expect(deletedPill?.textContent).toBe('deleted.ts'); + }); + + it('should not dispatch click events when clickable is false', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + body: 'Files', + fileList: { + filePaths: [ 'test.js' ], + details: { + 'test.js': { + clickable: false + } + }, + renderAsPills: true + } + } + } + }); + + const pillElement = card.render.querySelector('.mynah-chat-item-tree-file-pill') as HTMLElement; + pillElement?.click(); + + expect(mockDispatch).not.toHaveBeenCalled(); + }); + }); + + it('should not render pills when renderAsPills is false', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + body: 'Files', + fileList: { + filePaths: [ 'file1.ts' ], + renderAsPills: false + } + } + } + }); + + const pillElements = card.render.querySelectorAll('.mynah-chat-item-tree-file-pill'); + expect(pillElements.length).toBe(0); + }); + + it('should fall back to file path when visibleName is not provided', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + body: 'Files', + fileList: { + filePaths: [ 'src/components/test.ts' ], + renderAsPills: true + } + } + } + }); + + const pillElement = card.render.querySelector('.mynah-chat-item-tree-file-pill'); + expect(pillElement?.textContent).toBe('src/components/test.ts'); + }); + + it('should handle empty filePaths array', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + body: 'No files', + fileList: { + filePaths: [], + renderAsPills: true + } + } + } + }); + + const pillElements = card.render.querySelectorAll('.mynah-chat-item-tree-file-pill'); + expect(pillElements.length).toBe(0); + }); + + it('should parse markdown in header body for pills', () => { + const card = new ChatItemCard({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + header: { + body: 'Reading `inline code` text', + fileList: { + filePaths: [ 'test.js' ], + renderAsPills: true + } + } + } + }); + + const codeElement = card.render.querySelector('.mynah-inline-code'); + expect(codeElement).toBeTruthy(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-followup.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-followup.spec.ts new file mode 100644 index 00000000..a1e0c48e --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-followup.spec.ts @@ -0,0 +1,18 @@ +import { ChatItemFollowUpContainer } from '../../chat-item/chat-item-followup'; +import { ChatItemType } from '../../../static'; + +describe('ChatItemFollowUpContainer', () => { + it('should render followup container', () => { + const container = new ChatItemFollowUpContainer({ + tabId: 'test-tab', + chatItem: { + type: ChatItemType.ANSWER, + followUp: { + options: [] + } + } + }); + + expect(container.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-form-items.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-form-items.spec.ts new file mode 100644 index 00000000..326d939d --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-form-items.spec.ts @@ -0,0 +1,124 @@ +import { ChatItemFormItemsWrapper } from '../../chat-item/chat-item-form-items'; +import { ChatItem, ChatItemType } from '../../../static'; + +describe('ChatItemFormItemsWrapper', () => { + it('should render form items wrapper', () => { + const wrapper = new ChatItemFormItemsWrapper({ + tabId: 'test-tab', + chatItem: {} + }); + + expect(wrapper.render).toBeDefined(); + }); + + it('should disable radio input when disabled is true', () => { + const chatItem: ChatItem = { + type: ChatItemType.PROMPT, + formItems: [ + { + id: 'test-radio', + type: 'radiogroup', + title: 'Test Radio', + options: [ { value: 'option1', label: 'Option 1' } ], + disabled: true + } + ] + }; + + const wrapper = new ChatItemFormItemsWrapper({ + tabId: 'test-tab', + chatItem + }); + + const radioInput = wrapper.render.querySelector('.mynah-form-input[disabled]'); + expect(radioInput?.hasAttribute('disabled')).toBe(true); + }); + + it('should disable text input when disabled is true', () => { + const chatItem: ChatItem = { + type: ChatItemType.PROMPT, + formItems: [ + { + id: 'test-text', + type: 'textinput', + title: 'Test Text', + disabled: true + } + ] + }; + + const wrapper = new ChatItemFormItemsWrapper({ + tabId: 'test-tab', + chatItem + }); + + const textInput = wrapper.render.querySelector('input[type="text"][disabled]'); + expect(textInput?.hasAttribute('disabled')).toBe(true); + }); + + it('should disable numeric input when disabled is true', () => { + const chatItem: ChatItem = { + type: ChatItemType.PROMPT, + formItems: [ + { + id: 'test-numeric', + type: 'numericinput', + title: 'Test Numeric', + disabled: true + } + ] + }; + + const wrapper = new ChatItemFormItemsWrapper({ + tabId: 'test-tab', + chatItem + }); + + const numericInput = wrapper.render.querySelector('input[type="number"][disabled]'); + expect(numericInput?.hasAttribute('disabled')).toBe(true); + }); + + it('should disable email input when disabled is true', () => { + const chatItem: ChatItem = { + type: ChatItemType.PROMPT, + formItems: [ + { + id: 'test-email', + type: 'email', + title: 'Test Email', + disabled: true + } + ] + }; + + const wrapper = new ChatItemFormItemsWrapper({ + tabId: 'test-tab', + chatItem + }); + + const emailInput = wrapper.render.querySelector('input[type="email"][disabled]'); + expect(emailInput?.hasAttribute('disabled')).toBe(true); + }); + + it('should not disable form items when disabled is false or undefined', () => { + const chatItem: ChatItem = { + type: ChatItemType.PROMPT, + formItems: [ + { + id: 'test-enabled', + type: 'textinput', + title: 'Test Enabled', + disabled: false + } + ] + }; + + const wrapper = new ChatItemFormItemsWrapper({ + tabId: 'test-tab', + chatItem + }); + + const textInput = wrapper.render.querySelector('input[type="text"]'); + expect(textInput?.hasAttribute('disabled')).toBe(false); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-information-card.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-information-card.spec.ts new file mode 100644 index 00000000..0a0abe3f --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-information-card.spec.ts @@ -0,0 +1,33 @@ +import { ChatItemInformationCard } from '../../chat-item/chat-item-information-card'; + +// Mock the ChatItemCard component +jest.mock('../../chat-item/chat-item-card', () => ({ + ChatItemCard: jest.fn().mockImplementation(() => ({ + render: document.createElement('div') + })) +})); + +// Mock the TitleDescriptionWithIcon component +jest.mock('../../title-description-with-icon', () => ({ + TitleDescriptionWithIcon: jest.fn().mockImplementation(() => ({ + render: document.createElement('div') + })) +})); + +describe('ChatItemInformationCard', () => { + it('should render information card with basic properties', () => { + const card = new ChatItemInformationCard({ + tabId: 'test-tab', + messageId: 'test-message', + informationCard: { + title: 'Test Information', + content: { + body: 'Test content' + } + } + }); + + expect(card.render).toBeDefined(); + expect(card.render.classList.contains('mynah-chat-item-information-card')).toBe(true); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-relevance-vote.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-relevance-vote.spec.ts new file mode 100644 index 00000000..6b470406 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-relevance-vote.spec.ts @@ -0,0 +1,32 @@ +import { ChatItemRelevanceVote } from '../../chat-item/chat-item-relevance-vote'; + +// Mock the global events +jest.mock('../../../helper/events', () => ({ + MynahUIGlobalEvents: { + getInstance: jest.fn(() => ({ + dispatch: jest.fn(), + addListener: jest.fn().mockReturnValue('mock-listener-id'), + removeListener: jest.fn() + })) + } +})); + +describe('ChatItemRelevanceVote', () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should render relevance vote component', () => { + const vote = new ChatItemRelevanceVote({ + tabId: 'test-tab', + messageId: 'test-message' + }); + + expect(vote.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-source-links.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-source-links.spec.ts new file mode 100644 index 00000000..29d017a0 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-source-links.spec.ts @@ -0,0 +1,27 @@ +import { ChatItemSourceLinksContainer } from '../../chat-item/chat-item-source-links'; + +// Mock the Card component +jest.mock('../../card/card', () => ({ + Card: jest.fn().mockImplementation(() => ({ + render: document.createElement('div') + })) +})); + +// Mock the SourceLinkHeader component +jest.mock('../../source-link/source-link-header', () => ({ + SourceLinkHeader: jest.fn().mockImplementation(() => ({ + render: document.createElement('div') + })) +})); + +describe('ChatItemSourceLinksContainer', () => { + it('should not render when relatedContent is undefined', () => { + const container = new ChatItemSourceLinksContainer({ + tabId: 'test-tab', + messageId: 'test-message', + title: 'Related Links' + }); + + expect(container.render).toBeUndefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tabbed-card.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tabbed-card.spec.ts new file mode 100644 index 00000000..65e503dc --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tabbed-card.spec.ts @@ -0,0 +1,79 @@ +import { ChatItemTabbedCard } from '../../chat-item/chat-item-tabbed-card'; + +// Mock the ChatItemCard component +jest.mock('../../chat-item/chat-item-card', () => ({ + ChatItemCard: jest.fn().mockImplementation((props) => ({ + render: document.createElement('div'), + props, + clearContent: jest.fn(), + updateCardStack: jest.fn() + })) +})); + +// Mock the Tab component +jest.mock('../../tabs', () => ({ + Tab: jest.fn().mockImplementation(() => ({ + render: document.createElement('div') + })) +})); + +describe('ChatItemTabbedCard', () => { + it('should render tabbed card with multiple tabs', () => { + const tabbedCard = [ + { + label: 'Tab 1', + value: 'tab1', + content: { + body: 'Content 1' + } + }, + { + label: 'Tab 2', + value: 'tab2', + content: { + body: 'Content 2' + } + } + ]; + + const card = new ChatItemTabbedCard({ + tabId: 'test-tab', + messageId: 'test-message', + tabbedCard + }); + + expect(card.render).toBeDefined(); + + // Check that the first tab's content is rendered by default + expect(card.contentCard.props.chatItem.body).toBe('Content 1'); + }); + + it('should render tabbed card with selected tab', () => { + const tabbedCard = [ + { + label: 'Tab 1', + value: 'tab1', + content: { + body: 'Content 1' + } + }, + { + label: 'Tab 2', + value: 'tab2', + selected: true, + content: { + body: 'Content 2' + } + } + ]; + + const card = new ChatItemTabbedCard({ + tabId: 'test-tab', + messageId: 'test-message', + tabbedCard + }); + + // Check that the selected tab's content is rendered + expect(card.contentCard.props.chatItem.body).toBe('Content 2'); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-file.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-file.spec.ts new file mode 100644 index 00000000..d980f9a4 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-file.spec.ts @@ -0,0 +1,15 @@ +import { ChatItemTreeFile } from '../../chat-item/chat-item-tree-file'; + +describe('ChatItemTreeFile', () => { + it('should render tree file with basic properties', () => { + const treeFile = new ChatItemTreeFile({ + tabId: 'test-tab', + messageId: 'test-message', + filePath: '/src/test.ts', + originalFilePath: '/src/test.ts', + fileName: 'test.ts' + }); + + expect(treeFile.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view-license.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view-license.spec.ts new file mode 100644 index 00000000..fa1126a8 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view-license.spec.ts @@ -0,0 +1,12 @@ +import { ChatItemTreeViewLicense } from '../../chat-item/chat-item-tree-view-license'; + +describe('ChatItemTreeViewLicense', () => { + it('should render tree view license', () => { + const license = new ChatItemTreeViewLicense({ + referenceSuggestionLabel: 'Test Reference', + references: [] + }); + + expect(license.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view-wrapper.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view-wrapper.spec.ts new file mode 100644 index 00000000..efc22e4b --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view-wrapper.spec.ts @@ -0,0 +1,17 @@ +import { ChatItemTreeViewWrapper } from '../../chat-item/chat-item-tree-view-wrapper'; + +describe('ChatItemTreeViewWrapper', () => { + it('should render tree view wrapper', () => { + const wrapper = new ChatItemTreeViewWrapper({ + tabId: 'test-tab', + messageId: 'test-message', + files: [], + deletedFiles: [], + referenceSuggestionLabel: 'Test Reference', + references: [], + onRootCollapsedStateChange: jest.fn() + }); + + expect(wrapper.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view.spec.ts new file mode 100644 index 00000000..bc045440 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-item-tree-view.spec.ts @@ -0,0 +1,21 @@ +import { ChatItemTreeView } from '../../chat-item/chat-item-tree-view'; + +describe('ChatItemTreeView', () => { + it('should render tree view with file node', () => { + const fileNode = { + type: 'file' as const, + name: 'test.ts', + filePath: '/src/test.ts', + originalFilePath: '/src/test.ts', + deleted: false + }; + + const treeView = new ChatItemTreeView({ + tabId: 'test-tab', + messageId: 'test-message', + node: fileNode + }); + + expect(treeView.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-command.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-command.spec.ts new file mode 100644 index 00000000..12593de7 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-command.spec.ts @@ -0,0 +1,47 @@ +import { ChatPromptInputCommand } from '../../chat-item/chat-prompt-input-command'; + +describe('chat-prompt-input-command', () => { + it('renders with command text', () => { + const command = new ChatPromptInputCommand({ + command: 'test-command', + onRemoveClick: () => {} + }); + + expect(command.render.querySelector('.mynah-chat-prompt-input-command-text')).toBeDefined(); + expect(command.render.classList.contains('hidden')).toBe(false); + }); + + it('handles empty command', () => { + const command = new ChatPromptInputCommand({ + command: '', + onRemoveClick: () => {} + }); + + expect(command.render.classList.contains('hidden')).toBe(true); + }); + + it('handles remove click', () => { + let removeClicked = false; + const command = new ChatPromptInputCommand({ + command: 'test-command', + onRemoveClick: () => { removeClicked = true; } + }); + + const textElement = command.render.querySelector('.mynah-chat-prompt-input-command-text') as HTMLElement; + textElement.click(); + expect(removeClicked).toBe(true); + }); + + it('sets command text', () => { + const command = new ChatPromptInputCommand({ + command: 'initial', + onRemoveClick: () => {} + }); + + command.setCommand('new-command'); + expect(command.render.classList.contains('hidden')).toBe(false); + + command.setCommand(''); + expect(command.render.classList.contains('hidden')).toBe(true); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-info.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-info.spec.ts new file mode 100644 index 00000000..fa6867c6 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-info.spec.ts @@ -0,0 +1,108 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { MynahEventNames } from '../../../static'; +import { ChatPromptInputInfo } from '../../chat-item/chat-prompt-input-info'; + +describe('chat-prompt-input-info', () => { + it('renders with info text', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputInfo: 'Test info message' + } + }) as string; + + const info = new ChatPromptInputInfo({ + tabId: testTabId + }); + + expect(info.render.querySelector('.mynah-chat-prompt-input-info')).toBeDefined(); + expect(info.render.textContent).toContain('Test info message'); + }); + + it('handles empty info', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputInfo: '' + } + }) as string; + + const info = new ChatPromptInputInfo({ + tabId: testTabId + }); + + expect(info.render.children.length).toBe(0); + }); + + it('updates info text', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputInfo: 'Initial info' + } + }) as string; + + const info = new ChatPromptInputInfo({ + tabId: testTabId + }); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { + promptInputInfo: 'Updated info' + } + }); + + expect(info.render.textContent).toContain('Updated info'); + }); + + it('clears info when set to null', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputInfo: 'Initial info' + } + }) as string; + + const info = new ChatPromptInputInfo({ + tabId: testTabId + }); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { + promptInputInfo: '' + } + }); + + expect(info.render.children.length).toBe(0); + }); + + it('handles link clicks', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputInfo: 'Test link' + } + }) as string; + + let linkClicked = false; + const originalDispatch = MynahUIGlobalEvents.getInstance().dispatch; + MynahUIGlobalEvents.getInstance().dispatch = (eventName: string) => { + if (eventName === MynahEventNames.INFO_LINK_CLICK) { + linkClicked = true; + } + }; + + const info = new ChatPromptInputInfo({ + tabId: testTabId + }); + + const link = info.render.querySelector('a') as HTMLElement; + if (link != null) { + link.click(); + expect(linkClicked).toBe(true); + } + + MynahUIGlobalEvents.getInstance().dispatch = originalDispatch; + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-sticky-card.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-sticky-card.spec.ts new file mode 100644 index 00000000..64a94a8f --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input-sticky-card.spec.ts @@ -0,0 +1,86 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { ChatPromptInputStickyCard } from '../../chat-item/chat-prompt-input-sticky-card'; + +describe('chat-prompt-input-sticky-card', () => { + it('renders with sticky card data', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputStickyCard: { + body: 'Test sticky card content', + messageId: 'test-message' + } + } + }) as string; + + const stickyCard = new ChatPromptInputStickyCard({ + tabId: testTabId + }); + + expect(stickyCard.render.querySelector('.mynah-chat-prompt-input-sticky-card')).toBeDefined(); + expect(stickyCard.render.textContent).toContain('Test sticky card content'); + }); + + it('handles null sticky card', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputStickyCard: null + } + }) as string; + + const stickyCard = new ChatPromptInputStickyCard({ + tabId: testTabId + }); + + expect(stickyCard.render.children.length).toBe(0); + }); + + it('updates sticky card content', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputStickyCard: { + body: 'Initial content' + } + } + }) as string; + + const stickyCard = new ChatPromptInputStickyCard({ + tabId: testTabId + }); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { + promptInputStickyCard: { + body: 'Updated content' + } + } + }); + + expect(stickyCard.render.textContent).toContain('Updated content'); + }); + + it('clears sticky card when set to null', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputStickyCard: { + body: 'Initial content' + } + } + }) as string; + + const stickyCard = new ChatPromptInputStickyCard({ + tabId: testTabId + }); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { + promptInputStickyCard: null + } + }); + + expect(stickyCard.render.children.length).toBe(0); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input.spec.ts new file mode 100644 index 00000000..4a676035 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-prompt-input.spec.ts @@ -0,0 +1,32 @@ +import { ChatPromptInput } from '../../chat-item/chat-prompt-input'; + +// Mock the tabs store +jest.mock('../../../helper/tabs-store', () => ({ + MynahUITabsStore: { + getInstance: jest.fn(() => ({ + getTabDataStore: jest.fn(() => ({ + subscribe: jest.fn(), + getValue: jest.fn((key: string) => { + // Return appropriate string values for string-expected keys + if (key === 'promptInputText' || key === 'promptInputLabel' || key === 'promptInputInfo') { + return ''; + } + // Return false for boolean keys + return false; + }), + updateStore: jest.fn() + })), + addListenerToDataStore: jest.fn() + })) + } +})); + +describe('ChatPromptInput', () => { + it('should render chat prompt input', () => { + const input = new ChatPromptInput({ + tabId: 'test-tab' + }); + + expect(input.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/chat-wrapper.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/chat-wrapper.spec.ts new file mode 100644 index 00000000..30de1d8e --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/chat-wrapper.spec.ts @@ -0,0 +1,32 @@ +import { ChatWrapper } from '../../chat-item/chat-wrapper'; + +// Mock the tabs store +jest.mock('../../../helper/tabs-store', () => ({ + MynahUITabsStore: { + getInstance: jest.fn(() => ({ + getTabDataStore: jest.fn(() => ({ + subscribe: jest.fn(), + getValue: jest.fn((key: string) => { + // Return appropriate string values for string-expected keys + if (key === 'promptInputInfo' || key === 'promptInputLabel' || key === 'promptInputText') { + return ''; + } + // Return empty object for other keys + return {}; + }), + updateStore: jest.fn() + })), + addListenerToDataStore: jest.fn() + })) + } +})); + +describe('ChatWrapper', () => { + it('should render chat wrapper', () => { + const wrapper = new ChatWrapper({ + tabId: 'test-tab' + }); + + expect(wrapper.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-attachment.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-attachment.spec.ts new file mode 100644 index 00000000..d5452225 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-attachment.spec.ts @@ -0,0 +1,64 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { PromptAttachment } from '../../chat-item/prompt-input/prompt-attachment'; + +describe('prompt-attachment', () => { + it('renders code attachment', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptAttachment({ + tabId: testTabId + }); + + attachment.updateAttachment('console.log("test");', 'code'); + expect(attachment.render.querySelector('.outer-container')).toBeDefined(); + expect(attachment.lastAttachmentContent).toContain('console.log("test");'); + }); + + it('handles markdown attachment', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptAttachment({ + tabId: testTabId + }); + + attachment.updateAttachment('# Test Markdown', 'markdown'); + expect(attachment.lastAttachmentContent).toContain('# Test Markdown'); + }); + + it('clears attachment', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptAttachment({ + tabId: testTabId + }); + + attachment.updateAttachment('test content', 'code'); + expect(attachment.lastAttachmentContent).not.toBe(''); + + attachment.clear(); + expect(attachment.lastAttachmentContent).toBe(''); + }); + + it('handles empty attachment', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptAttachment({ + tabId: testTabId + }); + + attachment.updateAttachment(undefined, 'code'); + expect(attachment.lastAttachmentContent).toBe(''); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-input-send-button.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-input-send-button.spec.ts new file mode 100644 index 00000000..e00c4e9e --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-input-send-button.spec.ts @@ -0,0 +1,47 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { PromptInputSendButton } from '../../chat-item/prompt-input/prompt-input-send-button'; + +describe('prompt-input-send-button', () => { + it('renders with correct attributes', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputDisabledState: false, + } + }) as string; + + let clicked = false; + const sendButton = new PromptInputSendButton({ + tabId: testTabId, + onClick: () => { clicked = true; } + }); + + expect(sendButton.render.querySelector('[data-testid="prompt-input-send-button"]')).toBeDefined(); + expect(sendButton.render.getAttribute('disabled')).toBe(null); + + sendButton.render.click(); + expect(clicked).toBe(true); + }); + + it('handles disabled state changes', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputDisabledState: true, + } + }) as string; + + const sendButton = new PromptInputSendButton({ + tabId: testTabId, + onClick: () => {} + }); + + expect(sendButton.render.getAttribute('disabled')).toBe('disabled'); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { promptInputDisabledState: false } + }); + + expect(sendButton.render.getAttribute('disabled')).toBe(null); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-input-stop-button.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-input-stop-button.spec.ts new file mode 100644 index 00000000..f186c7c3 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-input-stop-button.spec.ts @@ -0,0 +1,59 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { PromptInputStopButton } from '../../chat-item/prompt-input/prompt-input-stop-button'; + +describe('prompt-input-stop-button', () => { + it('renders with correct visibility states', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + cancelButtonWhenLoading: false, + loadingChat: false, + } + }) as string; + + let clicked = false; + const stopButton = new PromptInputStopButton({ + tabId: testTabId, + onClick: () => { clicked = true; } + }); + + expect(stopButton.render.classList.contains('hidden')).toBe(true); + + stopButton.render.click(); + expect(clicked).toBe(true); + }); + + it('shows when loading and cancel button enabled', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + cancelButtonWhenLoading: true, + loadingChat: true, + } + }) as string; + + const stopButton = new PromptInputStopButton({ + tabId: testTabId, + onClick: () => {} + }); + + expect(stopButton.render.classList.contains('hidden')).toBe(false); + }); + + it('hides when not loading', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + cancelButtonWhenLoading: true, + loadingChat: false, + } + }) as string; + + const stopButton = new PromptInputStopButton({ + tabId: testTabId, + onClick: () => {} + }); + + expect(stopButton.render.classList.contains('hidden')).toBe(true); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-options.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-options.spec.ts new file mode 100644 index 00000000..bb708228 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-options.spec.ts @@ -0,0 +1,54 @@ +import { PromptOptions } from '../../chat-item/prompt-input/prompt-options'; + +describe('prompt-options', () => { + it('renders with filter options', () => { + const filterOptions = [ { + id: 'test-select', + type: 'select' as const, + title: 'Test Select', + options: [ + { value: 'option1', label: 'Option 1' }, + { value: 'option2', label: 'Option 2' } + ] + } ]; + + const promptOptions = new PromptOptions({ + filterOptions, + buttons: [], + onFiltersChange: () => {} + }); + + expect(promptOptions.render.querySelector('[data-testid="prompt-input-options"]')).toBeDefined(); + expect(promptOptions.render.querySelector('select')).toBeDefined(); + }); + + it('renders with buttons', () => { + const buttons = [ { + id: 'test-button', + text: 'Test Button', + status: 'info' as const + } ]; + + let clickedButtonId = ''; + const promptOptions = new PromptOptions({ + filterOptions: [], + buttons, + onButtonClick: (buttonId) => { clickedButtonId = buttonId; } + }); + + const button = promptOptions.render.querySelector('button'); + expect(button).toBeDefined(); + + button?.click(); + expect(clickedButtonId).toBe('test-button'); + }); + + it('handles empty options', () => { + const promptOptions = new PromptOptions({ + filterOptions: [], + buttons: [] + }); + + expect(promptOptions.render.children.length).toBeGreaterThanOrEqual(0); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-progress.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-progress.spec.ts new file mode 100644 index 00000000..cf0336a1 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-progress.spec.ts @@ -0,0 +1,77 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { PromptInputProgress } from '../../chat-item/prompt-input/prompt-progress'; + +describe('prompt-input-progress', () => { + it('renders with progress data', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputProgress: { + value: 75, + text: 'Processing request...', + actions: [ { + id: 'cancel', + text: 'Cancel' + } ] + } + } + }) as string; + + const progressInput = new PromptInputProgress({ + tabId: testTabId + }); + + expect(progressInput.render.querySelector('[data-testid="prompt-input-progress-wrapper"]')).toBeDefined(); + }); + + it('handles progress updates', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputProgress: null + } + }) as string; + + const progressInput = new PromptInputProgress({ + tabId: testTabId + }); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { + promptInputProgress: { + value: 50, + text: 'Updated progress' + } + } + }); + + expect(progressInput.render).toBeDefined(); + }); + + it('handles action clicks', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputProgress: { + value: 25, + text: 'Loading...', + actions: [ { + id: 'stop', + text: 'Stop' + } ] + } + } + }) as string; + + const originalDispatch = MynahUIGlobalEvents.getInstance().dispatch; + MynahUIGlobalEvents.getInstance().dispatch = () => {}; + + const progressInput = new PromptInputProgress({ + tabId: testTabId + }); + expect(progressInput).toBeDefined(); + + MynahUIGlobalEvents.getInstance().dispatch = originalDispatch; + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-text-attachment.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-text-attachment.spec.ts new file mode 100644 index 00000000..0559bc57 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-text-attachment.spec.ts @@ -0,0 +1,80 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { PromptTextAttachment } from '../../chat-item/prompt-input/prompt-text-attachment'; + +describe('prompt-text-attachment', () => { + it('renders with content', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptTextAttachment({ + tabId: testTabId, + content: 'test content', + type: 'markdown' + }); + + expect(attachment.render.querySelector('.mynah-chat-prompt-attachment')).toBeDefined(); + }); + + it('handles code type', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptTextAttachment({ + tabId: testTabId, + content: 'console.log("test");', + type: 'code' + }); + + expect(attachment.render.querySelector('.mynah-chat-prompt-attachment')).toBeDefined(); + }); + + it('handles remove click', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptTextAttachment({ + tabId: testTabId, + content: 'test content', + type: 'markdown' + }); + + expect(attachment.render).toBeDefined(); + }); + + it('clears content', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptTextAttachment({ + tabId: testTabId, + content: 'test content', + type: 'markdown' + }); + + attachment.clear(); + expect(attachment.render).toBeDefined(); + }); + + it('handles empty content', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const attachment = new PromptTextAttachment({ + tabId: testTabId, + content: '', + type: 'markdown' + }); + + expect(attachment.render).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-text-input.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-text-input.spec.ts new file mode 100644 index 00000000..bd4fea50 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-text-input.spec.ts @@ -0,0 +1,298 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { PromptTextInput } from '../../chat-item/prompt-input/prompt-text-input'; +import { Config } from '../../../helper/config'; + +describe('prompt-text-input', () => { + it('renders with correct attributes', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputPlaceholder: 'Enter your prompt...', + promptInputDisabledState: false, + } + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input'); + expect(inputElement?.getAttribute('placeholder')).toBe('Enter your prompt...'); + expect(inputElement?.getAttribute('contenteditable')).toBe('plaintext-only'); + expect(inputElement?.getAttribute('maxlength')).toBe('4000'); + }); + + it('handles disabled state', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputDisabledState: true, + } + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input'); + expect(inputElement?.getAttribute('disabled')).toBe('disabled'); + expect(inputElement?.getAttribute('contenteditable')).toBe('plaintext-only'); + expect(inputElement?.getAttribute('disabled')).toBe('disabled'); + }); + + it('handles focus and blur events', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + let focused = false; + let blurred = false; + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {}, + onFocus: () => { focused = true; }, + onBlur: () => { blurred = true; } + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input') as HTMLElement; + + inputElement.dispatchEvent(new FocusEvent('focus')); + expect(focused).toBe(true); + + inputElement.dispatchEvent(new FocusEvent('blur')); + expect(blurred).toBe(true); + }); + + it('handles input events', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + let inputCalled = false; + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {}, + onInput: () => { inputCalled = true; } + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input') as HTMLElement; + inputElement.dispatchEvent(new Event('input')); + expect(inputCalled).toBe(true); + }); + + it('updates placeholder text', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputPlaceholder: 'Initial placeholder' + } + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { + promptInputPlaceholder: 'Updated placeholder' + } + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input'); + expect(inputElement?.getAttribute('placeholder')).toBe('Updated placeholder'); + }); + + it('handles paste events', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input') as HTMLElement; + + inputElement.dispatchEvent(new Event('paste')); + expect(textInput).toBeDefined(); + }); + + it('manages text input value and clearing', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + // Test that methods exist and can be called + textInput.updateTextInputValue('test value'); + textInput.clear(); + expect(textInput.getTextInputValue()).toBe(''); + }); + + it('handles focus and blur methods', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + Config.getInstance().config.autoFocus = true; + textInput.focus(); + textInput.blur(); + + expect(textInput).toBeDefined(); + }); + + it('updates max length', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + textInput.updateTextInputMaxLength(2000); + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input'); + expect(inputElement?.getAttribute('maxlength')).toBe('2000'); + }); + + it('inserts context items', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + const contextItem = { + command: 'test-command', + description: 'Test command' + }; + + textInput.insertContextItem(contextItem, 0); + expect(textInput.getUsedContext()).toBeDefined(); + }); + + it('deletes text range', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + textInput.updateTextInputValue('hello world'); + textInput.deleteTextRange(0, 5); + expect(textInput).toBeDefined(); + }); + + it('inserts end space', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + textInput.updateTextInputValue('test'); + textInput.insertEndSpace(); + expect(textInput).toBeDefined(); + }); + + it('handles keydown events', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + let keydownCalled = false; + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => { keydownCalled = true; } + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input') as HTMLElement; + inputElement.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); + expect(keydownCalled).toBe(true); + }); + + it('handles disabled state changes', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: { + promptInputDisabledState: false + } + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + MynahUITabsStore.getInstance().updateTab(testTabId, { + store: { + promptInputDisabledState: true + } + }); + + const inputElement = textInput.render.querySelector('.mynah-chat-prompt-input'); + expect(inputElement?.getAttribute('disabled')).toBe('disabled'); + }); + + it('gets cursor position', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const textInput = new PromptTextInput({ + tabId: testTabId, + initMaxLength: 1000, + onKeydown: () => {} + }); + + expect(textInput.getCursorPos()).toBe(0); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-top-bar-button.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-top-bar-button.spec.ts new file mode 100644 index 00000000..efc78635 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-top-bar-button.spec.ts @@ -0,0 +1,50 @@ +import { TopBarButton } from '../../chat-item/prompt-input/prompt-top-bar/top-bar-button'; + +describe('top-bar-button', () => { + it('renders without button', () => { + const topBarButton = new TopBarButton({ + topBarButton: undefined, + onTopBarButtonClick: undefined + }); + + expect(topBarButton.render).toBeDefined(); + }); + + it('renders with button', () => { + const button = { + id: 'test-button', + text: 'Test Button', + status: 'info' as const + }; + + let buttonClicked = false; + const topBarButton = new TopBarButton({ + topBarButton: button, + onTopBarButtonClick: () => { buttonClicked = true; } + }); + + expect(topBarButton.render.classList.contains('hidden')).toBe(false); + + const buttonElement = topBarButton.render.querySelector('.mynah-button') as HTMLElement; + if (buttonElement != null) { + buttonElement.click(); + expect(buttonClicked).toBe(true); + } + }); + + it('handles different button states', () => { + const button = { + id: 'test-button', + text: 'Test Button', + status: 'success' as const + }; + + const topBarButton = new TopBarButton({ + topBarButton: button, + onTopBarButtonClick: () => {} + }); + + expect(topBarButton.render.classList.contains('hidden')).toBe(false); + expect(topBarButton.render.querySelector('.mynah-button')).toBeDefined(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/chat-item/prompt-top-bar.spec.ts b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-top-bar.spec.ts new file mode 100644 index 00000000..d2e99bea --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/chat-item/prompt-top-bar.spec.ts @@ -0,0 +1,96 @@ +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { PromptTopBar } from '../../chat-item/prompt-input/prompt-top-bar/prompt-top-bar'; + +describe('prompt-top-bar', () => { + it('renders with title', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + let titleClicked = false; + const topBar = new PromptTopBar({ + tabId: testTabId, + title: 'Test Title', + onTopBarTitleClick: () => { titleClicked = true; } + }); + + expect(topBar.render.querySelector('[data-testid="prompt-input-top-bar"]')).toBeDefined(); + + const titleButton = topBar.render.querySelector('button'); + titleButton?.click(); + expect(titleClicked).toBe(true); + }); + + it('renders with context items', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const contextItems = [ { + command: 'test-context', + description: 'Test Context Item' + } ]; + + const topBar = new PromptTopBar({ + tabId: testTabId, + contextItems, + onContextItemAdd: () => {}, + onContextItemRemove: () => {} + }); + + expect(topBar.render.querySelector('.mynah-prompt-input-top-bar')).toBeDefined(); + }); + + it('renders with top bar button', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const topBarButton = { + id: 'test-button', + text: 'Test Button' + }; + + const topBar = new PromptTopBar({ + tabId: testTabId, + topBarButton, + onTopBarButtonClick: () => {} + }); + + expect(topBar.topBarButton).toBeDefined(); + }); + + it('handles hidden state', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const topBar = new PromptTopBar({ + tabId: testTabId + }); + + expect(topBar.render.classList.contains('hidden')).toBe(true); + }); + + it('shows when title or context items provided', () => { + const testTabId = MynahUITabsStore.getInstance().addTab({ + isSelected: true, + store: {} + }) as string; + + const topBar = new PromptTopBar({ + tabId: testTabId, + title: 'Test Title', + contextItems: [ { + command: 'test', + description: 'Test' + } ] + }); + + expect(topBar.render.classList.contains('hidden')).toBe(false); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/feedback-form/feedback-form.spec.ts b/vendor/mynah-ui/src/components/__test__/feedback-form/feedback-form.spec.ts new file mode 100644 index 00000000..e0b595cc --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/feedback-form/feedback-form.spec.ts @@ -0,0 +1,65 @@ +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { MynahEventNames } from '../../../static'; +import { FeedbackForm } from '../../feedback-form/feedback-form'; + +describe('feedback form', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + it('submit', () => { + const testFeedbackForm = new FeedbackForm({ + initPayload: { + selectedOption: 'buggy-code', + comment: 'test comment', + messageId: 'test-message-id', + tabId: 'test-tab-id', + } + }); + + const spyDispatch = jest.spyOn(MynahUIGlobalEvents.getInstance(), 'dispatch'); + + // Actually render the portal + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.SHOW_FEEDBACK_FORM, { + messageId: 'test-message-id', + tabId: 'test-tab-id', + }); + + const submitButtonElement = testFeedbackForm.defaultFeedbackFormItems[testFeedbackForm.defaultFeedbackFormItems.length - 1].querySelectorAll('button')[1]; + expect(submitButtonElement.textContent).toBe('Submit'); + submitButtonElement.click(); + expect(spyDispatch).toHaveBeenCalledTimes(4); + expect(spyDispatch).toHaveBeenNthCalledWith(1, MynahEventNames.SHOW_FEEDBACK_FORM, { + messageId: 'test-message-id', + tabId: 'test-tab-id', + }); + }); + + it('cancel', () => { + const testFeedbackForm = new FeedbackForm({ + initPayload: { + selectedOption: 'buggy-code', + comment: 'test comment', + messageId: 'test-message-id', + tabId: 'test-tab-id', + } + }); + + const spyDispatch = jest.spyOn(MynahUIGlobalEvents.getInstance(), 'dispatch'); + + // Actually render the portal + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.SHOW_FEEDBACK_FORM, { + messageId: 'test-message-id', + tabId: 'test-tab-id', + }); + + const cancelButtonElement = testFeedbackForm.defaultFeedbackFormItems[testFeedbackForm.defaultFeedbackFormItems.length - 1].querySelectorAll('button')[0]; + expect(cancelButtonElement.textContent).toBe('Cancel'); + cancelButtonElement.click(); + expect(spyDispatch).toHaveBeenCalledTimes(4); + expect(spyDispatch).toHaveBeenNthCalledWith(1, MynahEventNames.SHOW_FEEDBACK_FORM, { + messageId: 'test-message-id', + tabId: 'test-tab-id', + }); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/icon.spec.ts b/vendor/mynah-ui/src/components/__test__/icon.spec.ts new file mode 100644 index 00000000..a2aa4e91 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/icon.spec.ts @@ -0,0 +1,106 @@ +import { Icon, MynahIcons } from '../icon'; + +describe('icon', () => { + it('renders predefined icon', () => { + const testIcon = new Icon({ + icon: MynahIcons.DESKTOP, + }); + + expect(testIcon.render).toBeDefined(); + expect(testIcon.render.classList.contains('mynah-ui-icon')).toBeTruthy(); + expect(testIcon.render.classList.contains('mynah-ui-icon-desktop')).toBeTruthy(); + }); + + it('renders globe icon', () => { + const testIcon = new Icon({ + icon: MynahIcons.GLOBE, + }); + + expect(testIcon.render).toBeDefined(); + expect(testIcon.render.classList.contains('mynah-ui-icon-globe')).toBeTruthy(); + }); + + it('renders icon with subtract', () => { + const testIcon = new Icon({ + icon: MynahIcons.DESKTOP, + subtract: true, + }); + + expect(testIcon.render.classList.contains('mynah-ui-icon-desktop-subtract')).toBeTruthy(); + }); + + it('renders icon with status', () => { + const testIcon = new Icon({ + icon: MynahIcons.GLOBE, + status: 'success', + }); + + expect(testIcon.render.classList.contains('status-success')).toBeTruthy(); + }); + + it('renders icon with custom classNames', () => { + const testIcon = new Icon({ + icon: MynahIcons.DESKTOP, + classNames: [ 'custom-class-1', 'custom-class-2' ], + }); + + expect(testIcon.render.classList.contains('custom-class-1')).toBeTruthy(); + expect(testIcon.render.classList.contains('custom-class-2')).toBeTruthy(); + }); + + it('updates icon', () => { + const testIcon = new Icon({ + icon: MynahIcons.DESKTOP, + }); + + expect(testIcon.render.classList.contains('mynah-ui-icon-desktop')).toBeTruthy(); + + testIcon.update(MynahIcons.GLOBE); + + expect(testIcon.render.classList.contains('mynah-ui-icon-desktop')).toBeFalsy(); + expect(testIcon.render.classList.contains('mynah-ui-icon-globe')).toBeTruthy(); + }); + + it('updates icon with subtract', () => { + const testIcon = new Icon({ + icon: MynahIcons.DESKTOP, + subtract: true, + }); + + expect(testIcon.render.classList.contains('mynah-ui-icon-desktop-subtract')).toBeTruthy(); + + testIcon.update(MynahIcons.GLOBE); + + expect(testIcon.render.classList.contains('mynah-ui-icon-desktop-subtract')).toBeFalsy(); + expect(testIcon.render.classList.contains('mynah-ui-icon-globe-subtract')).toBeTruthy(); + }); + + it('renders custom icon', () => { + const customIcon = { + name: 'custom-test-icon', + base64Svg: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjwvc3ZnPg==', + }; + + const testIcon = new Icon({ + icon: customIcon, + }); + + expect(testIcon.render.classList.contains('mynah-ui-icon-custom-test-icon')).toBeTruthy(); + }); + + it('updates from predefined to custom icon', () => { + const testIcon = new Icon({ + icon: MynahIcons.DESKTOP, + }); + + const customIcon = { + name: 'custom-icon', + base64Svg: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTYiPjwvc3ZnPg==', + }; + + testIcon.update(customIcon); + + expect(testIcon.render.classList.contains('mynah-ui-icon-desktop')).toBeFalsy(); + expect(testIcon.render.classList.contains('mynah-ui-icon-custom-icon')).toBeTruthy(); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/notification.spec.ts b/vendor/mynah-ui/src/components/__test__/notification.spec.ts new file mode 100644 index 00000000..8dd5aeae --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/notification.spec.ts @@ -0,0 +1,21 @@ +import { Notification } from '../notification'; + +describe('notification', () => { + it('notify', () => { + const mockClickHandler = jest.fn(); + const mockCloseHandler = jest.fn(); + const testNotification = new Notification({ + title: 'test notification title', + content: 'test notification content', + onNotificationClick: mockClickHandler, + onNotificationHide: mockCloseHandler, + }); + testNotification.notify(); + const notificationElement: HTMLDivElement | null = document.body.querySelector('.mynah-notification'); + expect(notificationElement?.querySelector('h3')?.textContent).toBe('test notification title'); + expect(notificationElement?.querySelector('.mynah-notification-content')?.textContent).toBe('test notification content'); + notificationElement?.click(); + expect(mockClickHandler).toHaveBeenCalledTimes(1); + expect(mockCloseHandler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/syntax-highlighter.spec.ts b/vendor/mynah-ui/src/components/__test__/syntax-highlighter.spec.ts new file mode 100644 index 00000000..5c47e0a3 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/syntax-highlighter.spec.ts @@ -0,0 +1,38 @@ +import { SyntaxHighlighter } from '../syntax-highlighter'; + +describe('syntax-highlighter', () => { + it('render', () => { + const testSyntaxHighlighter = new SyntaxHighlighter({ + codeStringWithMarkup: 'alert("hello");\n', + language: 'js', + block: true, + }); + + expect(testSyntaxHighlighter.render.outerHTML.replace('\n', '')).toBe( + '
alert("hello");
js
' + ); + }); + + it('should show buttons if showCopyButtons true and related events are connected', () => { + const testSyntaxHighlighter = new SyntaxHighlighter({ + codeStringWithMarkup: 'alert("hello");\n', + language: 'typescript', + codeBlockActions: { + copy: { + id: 'copy', + label: 'Copy' + }, + 'insert-at-cursor': { + id: 'insert-at-cursor', + label: 'Insert at cursor' + }, + }, + onCopiedToClipboard: () => {}, + onCodeBlockAction: () => {}, + block: true, + }); + expect(testSyntaxHighlighter.render.querySelectorAll('button')?.length).toBe(3); + expect(testSyntaxHighlighter.render.querySelectorAll('button')?.[1]?.textContent).toBe('Copy'); + expect(testSyntaxHighlighter.render.querySelectorAll('button')?.[2]?.textContent).toBe('Insert at cursor'); + }); +}); diff --git a/vendor/mynah-ui/src/components/__test__/toggle.spec.ts b/vendor/mynah-ui/src/components/__test__/toggle.spec.ts new file mode 100644 index 00000000..e3f1d2d0 --- /dev/null +++ b/vendor/mynah-ui/src/components/__test__/toggle.spec.ts @@ -0,0 +1,63 @@ +import { Tab } from '../tabs'; + +describe('toggle (tabs)', () => { + it('items', () => { + const testToggle = new Tab({ + options: [ + { + label: 'label1', + value: 'value1', + }, + { + label: 'label2', + value: 'value2', + }, + ], + direction: 'horizontal', + name: 'testToggle', + value: 'value2', + }); + + expect(testToggle.render.textContent).toContain('label1'); + expect(testToggle.render.textContent).toContain('label2'); + expect((testToggle.render.children[0].querySelector('input') as HTMLInputElement).getAttribute('checked')).toBeNull(); + // Second item should be currently selected + expect((testToggle.render.children[1].querySelector('input') as HTMLInputElement).getAttribute('checked')).toBe('checked'); + }); + + it('event handler', () => { + const mockOnChangeHandler = jest.fn(); + const mockOnRemoveHandler = jest.fn(); + + const testToggle = new Tab({ + options: [], + direction: 'horizontal', + name: 'testToggle', + onChange: mockOnChangeHandler, + onRemove: mockOnRemoveHandler, + }); + + testToggle.addOption({ + label: 'label1', + value: 'value1', + }); + testToggle.addOption({ + label: 'label2', + value: 'value2', + }); + testToggle.setValue('value2'); + + // Try to click and select the first item + document.body.appendChild(testToggle.render as HTMLElement); + const firstItemElement = document.querySelector('span[key="testToggle-value1"]') as HTMLElement; + (firstItemElement.querySelector('input') as HTMLInputElement).click(); + expect(mockOnChangeHandler).toHaveBeenCalledTimes(1); + expect(mockOnChangeHandler).toHaveBeenCalledWith('value1'); + + // Try to click the remove button on the second item + const secondItemElement = document.querySelector('span[key="testToggle-value2"]') as HTMLInputElement; + (secondItemElement.querySelector('button') as HTMLButtonElement).click(); + expect(mockOnRemoveHandler).toHaveBeenCalledTimes(1); + expect(mockOnRemoveHandler).toHaveBeenCalledWith('value2', expect.anything()); + }); +}); diff --git a/vendor/mynah-ui/src/components/background.ts b/vendor/mynah-ui/src/components/background.ts new file mode 100644 index 00000000..b1d0b1f3 --- /dev/null +++ b/vendor/mynah-ui/src/components/background.ts @@ -0,0 +1,68 @@ +/* eslint-disable @typescript-eslint/no-extraneous-class */ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { StyleLoader } from '../helper/style-loader'; + +export class GradientBackground { + render: ExtendedHTMLElement; + constructor () { + StyleLoader.getInstance().load('components/_background.scss'); + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-ui-gradient-background' ], + innerHTML: ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` + }); + } +} diff --git a/vendor/mynah-ui/src/components/button.ts b/vendor/mynah-ui/src/components/button.ts new file mode 100644 index 00000000..62f290fd --- /dev/null +++ b/vendor/mynah-ui/src/components/button.ts @@ -0,0 +1,292 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + DomBuilder, + DomBuilderEventHandler, + DomBuilderEventHandlerWithOptions, + DomBuilderObject, + ExtendedHTMLElement, + GenericEvents +} from '../helper/dom'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from './overlay'; +import { Card } from './card/card'; +import { CardBody } from './card/card-body'; +import { Config } from '../helper/config'; +import { cancelEvent } from '../helper/events'; +import escapeHTML from 'escape-html'; +import unescapeHTML from 'unescape-html'; +import { parseMarkdown } from '../helper/marked'; +import { StyleLoader } from '../helper/style-loader'; +import { Icon } from './icon'; + +const TOOLTIP_DELAY = 350; +export interface ButtonProps { + classNames?: string[]; + attributes?: Record; + icon?: HTMLElement | ExtendedHTMLElement; + testId?: string; + label?: HTMLElement | ExtendedHTMLElement | string; + confirmation?: { + confirmButtonText: string; + cancelButtonText: string; + title: string; + description?: string; + }; + tooltip?: string; + tooltipVerticalDirection?: OverlayVerticalDirection; + tooltipHorizontalDirection?: OverlayHorizontalDirection; + children?: Array; + disabled?: boolean; + hidden?: boolean; + primary?: boolean; + border?: boolean; + status?: 'main' | 'primary' | 'info' | 'success' | 'warning' | 'error' | 'clear' | 'dimmed-clear'; + fillState?: 'hover' | 'always'; + additionalEvents?: Partial>; + onClick: (e: Event) => void; + onHover?: (e: Event) => void; +} +export abstract class ButtonAbstract { + render: ExtendedHTMLElement; + updateLabel = (label: HTMLElement | ExtendedHTMLElement | string): void => { + }; + + setHidden = (hidden: boolean): void => { + }; + + setEnabled = (enabled: boolean): void => { + }; + + hideTooltip = (): void => { + }; +} + +class ButtonInternal extends ButtonAbstract { + render: ExtendedHTMLElement; + private readonly props: ButtonProps; + private tooltipOverlay: Overlay | null; + private tooltipTimeout: ReturnType; + constructor (props: ButtonProps) { + StyleLoader.getInstance().load('components/_button.scss'); + super(); + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'button', + classNames: [ + 'mynah-button', + ...(props.primary === false ? [ 'mynah-button-secondary' ] : []), + ...(props.border === true ? [ 'mynah-button-border' ] : []), + ...(props.hidden === true ? [ 'hidden' ] : []), + ...([ `fill-state-${props.fillState ?? 'always'}` ]), + ...(props.status != null ? [ `status-${props.status}` ] : []), + ...(props.classNames !== undefined ? props.classNames : []), + ], + testId: props.testId, + attributes: { + ...(props.disabled === true ? { disabled: 'disabled' } : {}), + tabindex: '0', + ...props.attributes, + }, + events: { + ...props.additionalEvents, + click: (e) => { + this.hideTooltip(); + cancelEvent(e); + if (this.props.disabled !== true) { + if (this.props.confirmation != null) { + const confirmationOverlay = new Overlay({ + onClose: () => { + }, + children: [ + { + type: 'div', + classNames: [ 'mynah-button-confirmation-dialog-container' ], + children: [ + { + type: 'div', + classNames: [ 'mynah-button-confirmation-dialog-header' ], + children: [ + new Icon({ icon: 'warning' }).render, + { + type: 'h4', + children: [ this.props.confirmation.title ] + }, + new Button({ + icon: new Icon({ icon: 'cancel' }).render, + onClick: () => { + confirmationOverlay.close(); + }, + primary: false, + status: 'clear', + }).render, + ] + }, + { + type: 'div', + classNames: [ 'mynah-button-confirmation-dialog-body' ], + innerHTML: parseMarkdown(this.props.confirmation.description ?? '') + }, + { + type: 'div', + classNames: [ 'mynah-button-confirmation-dialog-buttons' ], + children: [ + new Button({ + label: this.props.confirmation.cancelButtonText, + onClick: () => { + confirmationOverlay.close(); + }, + primary: false, + status: 'clear', + }).render, + new Button({ + label: this.props.confirmation.confirmButtonText, + onClick: () => { + confirmationOverlay.close(); + props.onClick(e); + }, + primary: true, + }).render + ] + } + ] + } + ], + background: true, + closeOnOutsideClick: false, + dimOutside: true, + horizontalDirection: OverlayHorizontalDirection.CENTER, + verticalDirection: OverlayVerticalDirection.CENTER, + referencePoint: { top: window.innerHeight / 2, left: window.innerWidth / 2 } + }); + } else { + props.onClick(e); + } + } + }, + mouseover: (e) => { + cancelEvent(e); + if (this.props.onHover != null) { + this.props.onHover(e); + } + const textContentSpan: HTMLSpanElement | null = this.render.querySelector('.mynah-button-label'); + let tooltipText; + if (props.label != null && typeof props.label === 'string' && textContentSpan != null && textContentSpan.offsetWidth < textContentSpan.scrollWidth) { + tooltipText = parseMarkdown(props.label ?? '', { includeLineBreaks: true }); + } + if (props.tooltip !== undefined) { + if (tooltipText != null) { + tooltipText += '\n\n'; + } else { + tooltipText = ''; + } + tooltipText += parseMarkdown(props.tooltip ?? '', { includeLineBreaks: true }); + } + if (tooltipText != null) { + this.showTooltip(tooltipText); + } + }, + mouseleave: this.hideTooltip + }, + children: [ + ...(props.icon !== undefined ? [ props.icon ] : []), + ...(this.getButtonLabelDomBuilderObject(props.label)), + ...(props.children ?? []), + ], + }); + } + + private readonly getButtonLabelDomBuilderObject = (label?: HTMLElement | ExtendedHTMLElement | string): DomBuilderObject[] => { + if (label !== undefined) { + if (typeof label !== 'string') { + return [ { type: 'span', classNames: [ 'mynah-button-label' ], children: [ label ] } ]; + } else { + return [ { type: 'span', classNames: [ 'mynah-button-label' ], innerHTML: parseMarkdown(unescapeHTML(escapeHTML(label)), { inline: true }) } ]; + } + } + return []; + }; + + private readonly showTooltip = (content: string): void => { + if (content.trim() !== undefined) { + clearTimeout(this.tooltipTimeout); + this.tooltipTimeout = setTimeout(() => { + const elm: HTMLElement = this.render; + this.tooltipOverlay = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: this.props.tooltipVerticalDirection ?? OverlayVerticalDirection.TO_TOP, + horizontalDirection: this.props.tooltipHorizontalDirection ?? OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + new Card({ + border: false, + children: [ + new CardBody({ + body: content + }).render + ] + }).render + ], + }); + }, TOOLTIP_DELAY); + } + }; + + public readonly hideTooltip = (): void => { + clearTimeout(this.tooltipTimeout); + if (this.tooltipOverlay !== null) { + this.tooltipOverlay?.close(); + this.tooltipOverlay = null; + } + }; + + public readonly updateLabel = (label: HTMLElement | ExtendedHTMLElement | string): void => { + (this.render.querySelector('.mynah-button-label') as ExtendedHTMLElement).replaceWith( + DomBuilder.getInstance().build(this.getButtonLabelDomBuilderObject(label)[0]) + ); + }; + + public readonly setEnabled = (enabled: boolean): void => { + this.props.disabled = !enabled; + if (enabled) { + this.render.removeAttribute('disabled'); + } else { + this.render.setAttribute('disabled', 'disabled'); + } + }; + + public readonly setHidden = (hidden: boolean): void => { + this.props.hidden = hidden; + if (hidden) { + this.render.classList.add('hidden'); + } else { + this.render.classList.remove('hidden'); + } + }; +} + +export class Button extends ButtonAbstract { + render: ExtendedHTMLElement; + + constructor (props: ButtonProps) { + super(); + return (new (Config.getInstance().config.componentClasses.Button ?? ButtonInternal)(props)); + } + + updateLabel = (label: HTMLElement | ExtendedHTMLElement | string): void => { + }; + + setEnabled = (enabled: boolean): void => { + }; + + setHidden = (hidden: boolean): void => { + }; + + hideTooltip = (): void => { + }; +} diff --git a/vendor/mynah-ui/src/components/card/card-body.ts b/vendor/mynah-ui/src/components/card/card-body.ts new file mode 100644 index 00000000..8ea8ec48 --- /dev/null +++ b/vendor/mynah-ui/src/components/card/card-body.ts @@ -0,0 +1,318 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { cleanupElement, DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { + CodeBlockActions, + OnCodeBlockActionFunction, + OnCopiedToClipboardFunction, + ReferenceTrackerInformation, +} from '../../static'; +import unescapeHTML from 'unescape-html'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; +import { SyntaxHighlighter } from '../syntax-highlighter'; +import { generateUID } from '../../helper/guid'; +import { Config } from '../../helper/config'; +import { parseMarkdown } from '../../helper/marked'; +import { StyleLoader } from '../../helper/style-loader'; + +const PREVIEW_DELAY = 500; + +export const highlightersWithTooltip = { + start: { + markupStart: ' `marker-index=${markerIndex}`, + markupEnd: ' reference-tracker>' + }, + end: { + markup: '', + }, +}; + +export const PARTS_CLASS_NAME = 'typewriter-part'; +export const PARTS_CLASS_NAME_VISIBLE = 'typewriter'; + +export interface CardBodyProps { + body?: string; + testId?: string; + children?: Array; + childLocation?: 'above-body' | 'below-body'; + highlightRangeWithTooltip?: ReferenceTrackerInformation[] | null; + hideCodeBlockLanguage?: boolean; + wrapCode?: boolean; + unlimitedCodeBlockHeight?: boolean; + codeBlockActions?: CodeBlockActions; + useParts?: boolean; + codeBlockStartIndex?: number; + processChildren?: boolean; + classNames?: string[]; + onLinkClick?: (url: string, e: MouseEvent) => void; + onCopiedToClipboard?: OnCopiedToClipboardFunction; + onCodeBlockAction?: OnCodeBlockActionFunction; +} +export class CardBody { + render: ExtendedHTMLElement; + props: CardBodyProps; + nextCodeBlockIndex: number = 0; + codeBlockStartIndex: number = 0; + private highlightRangeTooltip: Overlay | null; + private highlightRangeTooltipTimeout: ReturnType; + constructor (props: CardBodyProps) { + StyleLoader.getInstance().load('components/card/_card.scss'); + this.codeBlockStartIndex = props.codeBlockStartIndex ?? 0; + this.props = props; + const bodyChildren = this.getContentBodyChildren(this.props); + const childList = [ + ...bodyChildren, + ...(this.props.children != null + ? this.props.processChildren === true + ? this.props.children.map((node, index) => { + const processedNode = this.processNode(node as HTMLElement); + processedNode.setAttribute?.('render-index', (bodyChildren.length + index).toString()); + cleanupElement(processedNode); + return processedNode; + }) + : this.props.children.map((node, index): HTMLElement => { + (node as HTMLElement)?.setAttribute?.('render-index', (bodyChildren.length + index).toString()); + return node as HTMLElement; + }) + : []) + ]; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: this.props.testId, + classNames: [ 'mynah-card-body', ...(this.props.classNames ?? []) ], + children: this.props.childLocation === 'above-body' ? childList.reverse() : childList, + }); + cleanupElement(this.render); + + Array.from(this.render.querySelectorAll('mark[reference-tracker]')).forEach((highlightRangeElement) => { + highlightRangeElement.addEventListener('mouseenter', (e) => { + const index = parseInt((e.target as HTMLElement).getAttribute('marker-index') ?? '0'); + if (props.highlightRangeWithTooltip?.[index] !== undefined) { + this.showHighlightRangeTooltip(e as MouseEvent, props.highlightRangeWithTooltip[index].information); + } + }); + highlightRangeElement.addEventListener('mouseleave', this.hideHighlightRangeTooltip); + }); + } + + private readonly processNode = (node: HTMLElement): HTMLElement => { + let elementFromNode: HTMLElement = node; + if (this.props.useParts === true && elementFromNode.nodeType === Node.TEXT_NODE && elementFromNode.textContent?.trim() !== '') { + elementFromNode = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'mynah-ui-animation-text-content' ], + children: elementFromNode.textContent?.split(' ').map(textPart => DomBuilder.getInstance().build({ + type: 'span', + classNames: [ PARTS_CLASS_NAME ], + children: [ textPart, ' ' ] + })) + }); + } else { + if (elementFromNode.tagName?.toLowerCase() === 'a') { + const url = elementFromNode.getAttribute('href') ?? ''; + return DomBuilder.getInstance().build( + { + type: 'a', + classNames: this.props.useParts === true ? [ PARTS_CLASS_NAME ] : [], + events: { + click: (e: MouseEvent) => { + if (this.props.onLinkClick !== undefined) { + this.props.onLinkClick(url, e); + } + }, + auxclick: (e: MouseEvent) => { + if (this.props.onLinkClick !== undefined) { + this.props.onLinkClick(url, e); + } + }, + }, + attributes: { href: elementFromNode.getAttribute('href') ?? '', target: '_blank' }, + innerHTML: elementFromNode.innerHTML, + }); + } + if ((elementFromNode.tagName?.toLowerCase() === 'pre' && elementFromNode.querySelector('code') !== null) || + elementFromNode.tagName?.toLowerCase() === 'code' + ) { + const isBlockCode = elementFromNode.tagName?.toLowerCase() === 'pre' || elementFromNode.innerHTML.match(/\r|\n/) !== null; + const codeElement = (elementFromNode.tagName?.toLowerCase() === 'pre' ? elementFromNode.querySelector('code') : elementFromNode); + const snippetLanguage = Array.from(codeElement?.classList ?? []).find(className => className.match('language-'))?.replace('language-', ''); + const codeString = codeElement?.innerHTML ?? ''; + + const highlighter = new SyntaxHighlighter({ + codeStringWithMarkup: unescapeHTML(codeString), + language: snippetLanguage?.trim() !== '' ? snippetLanguage : '', + hideLanguage: this.props.hideCodeBlockLanguage, + wrapCodeBlock: this.props.wrapCode, + unlimitedHeight: this.props.unlimitedCodeBlockHeight, + codeBlockActions: !isBlockCode + ? undefined + : { + ...Config.getInstance().config.codeBlockActions, + ...this.props.codeBlockActions + }, + block: isBlockCode, + index: isBlockCode ? this.nextCodeBlockIndex : undefined, + onCopiedToClipboard: this.props.onCopiedToClipboard != null + ? (type, text, codeBlockIndex) => { + if (this.props.onCopiedToClipboard != null) { + this.props.onCopiedToClipboard( + type, + text, + this.getReferenceTrackerInformationFromElement(highlighter), + this.codeBlockStartIndex + (codeBlockIndex ?? 0), + this.nextCodeBlockIndex); + } + } + : undefined, + onCodeBlockAction: this.props.onCodeBlockAction != null + ? (actionId, data, type, text, refTracker, codeBlockIndex) => { + this.props.onCodeBlockAction?.( + actionId, + data, + type, + text, + this.getReferenceTrackerInformationFromElement(highlighter), + this.codeBlockStartIndex + (codeBlockIndex ?? 0), + this.nextCodeBlockIndex); + } + : undefined + }).render; + if (this.props.useParts === true) { + highlighter.classList.add(PARTS_CLASS_NAME); + } + if (isBlockCode) { + ++this.nextCodeBlockIndex; + } + return highlighter; + } + + elementFromNode.childNodes?.forEach((child) => { + elementFromNode.replaceChild(this.processNode(child as HTMLElement), child); + }); + } + return elementFromNode; + }; + + private readonly getReferenceTrackerInformationFromElement = (element: ExtendedHTMLElement | HTMLElement): ReferenceTrackerInformation[] => { + // cloning the element + // since we're gonna inject some unique items + // to get the start indexes + const codeElement = element.querySelector('code')?.cloneNode(true) as HTMLElement; + + if (codeElement !== undefined) { + const markerElements = codeElement.querySelectorAll('mark[reference-tracker]'); + if (markerElements.length > 0) { + return (Array.from(markerElements) as HTMLElement[]).map((mark: HTMLElement, index: number) => { + // Generating a unique identifier element + // to get the start index of it inside the code block + const startIndexText = `__MARK${index}_${generateUID()}_START__`; + const startIndexTextElement = DomBuilder.getInstance().build({ + type: 'span', + innerHTML: startIndexText + }); + // Injecting that unique identifier for the start index inside the current mark element + mark.insertAdjacentElement('afterbegin', startIndexTextElement); + // finding that text inside the code element's inner text + // to get the startIndex + const startIndex = codeElement.innerText.indexOf(startIndexText); + + // when we get the start index, we need to remove the element + // to get the next one's start index properly + // we don't need to calculate the end index because it will be available + startIndexTextElement.remove(); + + // find the original reference tracker information + const originalRefTrackerInfo = this.props.highlightRangeWithTooltip?.[parseInt(mark.getAttribute('marker-index') ?? '0')]; + return { + ...originalRefTrackerInfo, + recommendationContentSpan: { + start: startIndex, + end: startIndex + ( + (originalRefTrackerInfo?.recommendationContentSpan?.end ?? 0) - + (originalRefTrackerInfo?.recommendationContentSpan?.start ?? 0)) + } + }; + }) as ReferenceTrackerInformation[]; + } + } + + return []; + }; + + private readonly showHighlightRangeTooltip = (e: MouseEvent, tooltipText: string): void => { + clearTimeout(this.highlightRangeTooltipTimeout); + this.highlightRangeTooltipTimeout = setTimeout(() => { + this.highlightRangeTooltip = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: (e.currentTarget ?? e.target) as HTMLElement, + removeOtherOverlays: true, + dimOutside: false, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + { + type: 'div', + classNames: [ 'mynah-ui-syntax-highlighter-highlight-tooltip' ], + children: [ + new CardBody({ + body: tooltipText, + }).render + ] + } + ], + }); + }, PREVIEW_DELAY); + }; + + private readonly hideHighlightRangeTooltip = (): void => { + clearTimeout(this.highlightRangeTooltipTimeout); + if (this.highlightRangeTooltip !== null) { + this.highlightRangeTooltip?.close(); + this.highlightRangeTooltip = null; + } + }; + + private readonly getContentBodyChildren = (props: CardBodyProps): Array => { + if (props.body != null && props.body.trim() !== '') { + let incomingBody = props.body; + if (props.body !== undefined && props.highlightRangeWithTooltip !== undefined && (props.highlightRangeWithTooltip?.length ?? -1) > 0) { + props.highlightRangeWithTooltip?.forEach((highlightRangeWithTooltip, index) => { + if (incomingBody !== undefined && highlightRangeWithTooltip.recommendationContentSpan !== undefined) { + const generatedStartMarkup = `${highlightersWithTooltip.start.markupStart}${highlightersWithTooltip.start.markupAttributes(index.toString())}${highlightersWithTooltip.start.markupEnd}`; + let calculatedStartIndex = (highlightRangeWithTooltip.recommendationContentSpan.start + (index * (generatedStartMarkup.length + highlightersWithTooltip.end.markup.length))); + let calculatedEndIndex = (calculatedStartIndex + generatedStartMarkup.length - highlightRangeWithTooltip.recommendationContentSpan.start) + highlightRangeWithTooltip.recommendationContentSpan.end; + if (calculatedEndIndex > incomingBody.length) { + calculatedStartIndex = incomingBody.length - 1; + } + if (calculatedEndIndex > incomingBody.length) { + calculatedEndIndex = incomingBody.length - 1; + } + incomingBody = incomingBody.slice(0, calculatedStartIndex) + generatedStartMarkup + incomingBody.slice(calculatedStartIndex); + incomingBody = incomingBody.slice(0, calculatedEndIndex) + highlightersWithTooltip.end.markup + incomingBody.slice(calculatedEndIndex); + } + }); + } + + return [ + ...(Array.from( + DomBuilder.getInstance().build({ + type: 'div', + innerHTML: `${parseMarkdown(incomingBody, { includeLineBreaks: true })}`, + }).childNodes + ).map((node, index) => { + const processedNode = this.processNode(node as HTMLElement); + processedNode.setAttribute?.('render-index', index.toString()); + cleanupElement(processedNode); + return processedNode; + })) + ]; + } + + return []; + }; +} diff --git a/vendor/mynah-ui/src/components/card/card.ts b/vendor/mynah-ui/src/components/card/card.ts new file mode 100644 index 00000000..00313e49 --- /dev/null +++ b/vendor/mynah-ui/src/components/card/card.ts @@ -0,0 +1,146 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { StyleLoader } from '../../helper/style-loader'; +import { EngagementType, Status } from '../../static'; + +/** + * We'll not consider it as an engagement if the total spend time is lower than below constant and won't trigger the event + */ +const ENGAGEMENT_DURATION_LIMIT = 3000; + +/** + * This 6(px) and 300(ms) are coming from a behavioral research and browser reaction to input devices to count the action as a mouse movement or a click event + */ +const ENGAGEMENT_MIN_SELECTION_DISTANCE = 6; +const ENGAGEMENT_MIN_CLICK_DURATION = 300; +export interface CardProps extends Partial { + border?: boolean; + background?: boolean; + status?: Status; + padding?: 'small' | 'medium' | 'large' | 'none'; + children?: Array; + onCardEngaged?: (engagement: { + engagementDurationTillTrigger: number; + engagementType: EngagementType; + totalMouseDistanceTraveled: { + x: number; + y: number; + }; + selectionDistanceTraveled?: { x: number; y: number; selectedText?: string | undefined }; + }) => void; +} +export class Card { + render: ExtendedHTMLElement; + private readonly props: CardProps; + private engagementStartTime: number = -1; + private totalMouseDistanceTraveled: { x: number; y: number } = { x: 0, y: 0 }; + private previousMousePosition!: { x: number; y: number }; + private mouseDownInfo!: { x: number; y: number; time: number }; + constructor (props: CardProps) { + StyleLoader.getInstance().load('components/card/_card.scss'); + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: this.props.testId, + classNames: [ + 'mynah-card', + `padding-${props.padding ?? 'medium'}`, + `status-${props.status ?? 'default'}`, + props.border !== false ? 'border' : '', + props.background !== false ? 'background' : '', + ...(props.classNames ?? []) + ], + persistent: props.persistent, + innerHTML: props.innerHTML, + children: [ + ...(props.children ?? []), + ], + events: { + ...props.events, + ...(props.onCardEngaged !== undefined + ? { + mouseenter: e => { + if (this.engagementStartTime === -1) { + this.engagementStartTime = new Date().getTime(); + this.previousMousePosition = { x: e.clientX, y: e.clientY }; + this.totalMouseDistanceTraveled = { x: 0, y: 0 }; + } + }, + mousemove: e => { + if (this.engagementStartTime === -1) { + this.engagementStartTime = new Date().getTime(); + } + this.totalMouseDistanceTraveled = { + x: + this.totalMouseDistanceTraveled.x + + Math.abs(e.clientX - this.previousMousePosition.x), + y: + this.totalMouseDistanceTraveled.y + + Math.abs(e.clientY - this.previousMousePosition.y), + }; + this.previousMousePosition = { x: e.clientX, y: e.clientY }; + }, + mousedown: e => { + this.mouseDownInfo = { x: e.clientX, y: e.clientY, time: new Date().getTime() }; + }, + mouseup: e => { + const mouseUpInfo = { x: e.clientX, y: e.clientY, time: new Date().getTime() }; + if ( + this.mouseDownInfo !== undefined && + (Math.abs(this.mouseDownInfo.x - mouseUpInfo.x) > ENGAGEMENT_MIN_SELECTION_DISTANCE || + Math.abs(this.mouseDownInfo.y - mouseUpInfo.y) > + ENGAGEMENT_MIN_SELECTION_DISTANCE) && + mouseUpInfo.time - this.mouseDownInfo.time > ENGAGEMENT_MIN_CLICK_DURATION + ) { + this.handleEngagement({ + x: Math.abs(this.mouseDownInfo.x - mouseUpInfo.x), + y: Math.abs(this.mouseDownInfo.y - mouseUpInfo.y), + selectedText: window?.getSelection()?.toString(), + }); + } + }, + mouseleave: () => { + const engagementEndTime = new Date().getTime(); + if (this.engagementStartTime !== -1 && engagementEndTime - this.engagementStartTime > ENGAGEMENT_DURATION_LIMIT) { + this.handleEngagement(); + } else { + this.resetEngagement(); + } + }, + } + : {}) + }, + attributes: props.attributes + }); + } + + private readonly resetEngagement = (): void => { + this.engagementStartTime = -1; + this.totalMouseDistanceTraveled = { x: 0, y: 0 }; + this.previousMousePosition = { x: 0, y: 0 }; + this.mouseDownInfo = { x: 0, y: 0, time: -1 }; + }; + + private readonly handleEngagement = (interactionDistanceTraveled?: { + x: number; + y: number; + selectedText?: string; + }): void => { + if (this.props.onCardEngaged !== undefined) { + this.props.onCardEngaged({ + engagementDurationTillTrigger: new Date().getTime() - this.engagementStartTime, + engagementType: + interactionDistanceTraveled !== undefined ? EngagementType.INTERACTION : EngagementType.TIME, + totalMouseDistanceTraveled: this.totalMouseDistanceTraveled, + selectionDistanceTraveled: + Boolean(interactionDistanceTraveled?.x ?? 0) && Boolean(interactionDistanceTraveled?.y) + ? interactionDistanceTraveled + : undefined, + }); + } + this.resetEngagement(); + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-buttons.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-buttons.ts new file mode 100644 index 00000000..368dd93d --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-buttons.ts @@ -0,0 +1,113 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import testIds from '../../helper/test-ids'; +import { ChatItemButton } from '../../static'; +import { Button } from '../button'; +import { Icon } from '../icon'; +import { OverlayHorizontalDirection } from '../overlay'; +import { ChatItemFormItemsWrapper } from './chat-item-form-items'; + +export interface ChatItemButtonsWrapperProps { + tabId?: string; + classNames?: string[]; + buttons: ChatItemButton[] | null; + formItems?: ChatItemFormItemsWrapper | null; + onActionClick?: (action: ChatItemButton, e?: Event) => void; + onAllButtonsDisabled?: () => void; +} +export class ChatItemButtonsWrapper { + private readonly props: ChatItemButtonsWrapperProps; + private readonly actions: Record = {}; + + render: ExtendedHTMLElement; + constructor (props: ChatItemButtonsWrapperProps) { + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.buttons.wrapper, + classNames: [ 'mynah-chat-item-buttons-container', ...(this.props.classNames ?? []) ], + children: this.props.buttons?.map(chatActionAction => { + const actionItem = new Button({ + testId: testIds.chatItem.buttons.button, + label: chatActionAction.text, + fillState: chatActionAction.fillState, + tooltip: chatActionAction.description, + tooltipHorizontalDirection: OverlayHorizontalDirection.CENTER, + icon: chatActionAction.icon != null ? new Icon({ icon: chatActionAction.icon }).render : undefined, + primary: chatActionAction.status === 'primary', + border: chatActionAction.status !== 'primary', + classNames: [ + ...(chatActionAction.flash != null ? [ 'mynah-button-flash-by-parent-focus', `animate-${chatActionAction.flash}` ] : [ '' ]) + ], + ...(chatActionAction.flash != null + ? { + onHover: (e) => { + if (e.target != null) { + (e.target as HTMLButtonElement).classList.remove('mynah-button-flash-by-parent-focus'); + } + } + } + : {}), + attributes: { + 'action-id': chatActionAction.id + }, + status: chatActionAction.status, + onClick: (e) => { + if (e.target != null) { + (e.target as HTMLButtonElement).classList.remove('mynah-button-flash-by-parent-focus'); + } + if (props.formItems != null) { + props.formItems.disableAll(); + } else { + this.disableAll(); + } + if (this.props.onActionClick != null) { + this.props.onActionClick(chatActionAction, e); + } + } + }); + if (chatActionAction.disabled === true) { + actionItem.setEnabled(false); + } + this.actions[chatActionAction.id] = { + data: chatActionAction, + element: actionItem, + }; + return actionItem.render; + }) + }); + if (props.formItems != null) { + this.handleValidationChange(props.formItems.isFormValid()); + props.formItems.onValidationChange = (isValid) => { + this.handleValidationChange(isValid); + }; + props.formItems.onAllFormItemsDisabled = () => { + this.disableAll(); + }; + } + } + + private readonly handleValidationChange = (isFormValid: boolean): void => { + Object.keys(this.actions).forEach(chatActionId => { + if (this.actions[chatActionId].data.waitMandatoryFormItems !== false) { + this.actions[chatActionId].element.setEnabled(isFormValid); + } + }); + }; + + private readonly disableAll = (): void => { + Object.keys(this.actions).forEach(chatActionId => { + if (this.actions[chatActionId].data.disabled !== false) { + this.actions[chatActionId].element.setEnabled(false); + } + }); + this.props.onAllButtonsDisabled?.(); + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-card-content.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-card-content.ts new file mode 100644 index 00000000..62990807 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-card-content.ts @@ -0,0 +1,195 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilderObject, ExtendedHTMLElement, getTypewriterPartsCss } from '../../helper/dom'; +import { CardRenderDetails, ChatItem, CodeBlockActions, OnCodeBlockActionFunction, OnCopiedToClipboardFunction, ReferenceTrackerInformation } from '../../static'; +import { CardBody } from '../card/card-body'; +import { generateUID } from '../../helper/guid'; +import { Config } from '../../helper/config'; +export interface ChatItemCardContentProps { + body?: string | null; + testId?: string; + renderAsStream?: boolean; + classNames?: string[]; + unlimitedCodeBlockHeight?: boolean; + hideCodeBlockLanguage?: boolean; + wrapCode?: boolean; + codeReference?: ReferenceTrackerInformation[] | null; + onAnimationStateChange?: (isAnimating: boolean) => void; + contentProperties?: { + codeBlockActions?: CodeBlockActions; + onLinkClick?: (url: string, e: MouseEvent) => void; + onCopiedToClipboard?: OnCopiedToClipboardFunction; + onCodeBlockAction?: OnCodeBlockActionFunction; + }; + children?: Array; +} +export class ChatItemCardContent { + private props: ChatItemCardContentProps; + render: ExtendedHTMLElement; + contentBody: CardBody | null = null; + private readonly updateStack: Array> = []; + private typewriterItemIndex: number = 0; + private readonly typewriterId: string = `typewriter-card-${generateUID()}`; + private lastAnimationDuration: number = 0; + private updateTimer: ReturnType | undefined; + private isStreamActive: boolean = true; + constructor (props: ChatItemCardContentProps) { + this.props = props; + this.contentBody = this.getCardContent(); + this.render = this.contentBody.render; + + if ((this.props.renderAsStream ?? false) && (this.props.body ?? '').trim() !== '') { + this.updateCardStack({}); + } + } + + private readonly getCardContent = (): CardBody => { + return new CardBody({ + body: this.props.body ?? '', + hideCodeBlockLanguage: this.props.hideCodeBlockLanguage, + wrapCode: this.props.wrapCode, + unlimitedCodeBlockHeight: this.props.unlimitedCodeBlockHeight, + testId: this.props.testId, + useParts: this.props.renderAsStream, + classNames: [ this.typewriterId, ...(this.props.classNames ?? []) ], + highlightRangeWithTooltip: this.props.codeReference, + children: this.props.children, + ...this.props.contentProperties, + }); + }; + + private readonly updateCard = (): void => { + if (this.updateTimer === undefined && this.updateStack.length > 0) { + const updateWith: Partial | undefined = this.updateStack.shift(); + if (updateWith !== undefined) { + this.props = { + ...this.props, + ...updateWith, + }; + + const newCardContent = this.getCardContent(); + const upcomingWords = Array.from(newCardContent.render.querySelectorAll('.typewriter-part') ?? []); + for (let i = 0; i < upcomingWords.length; i++) { + upcomingWords[i].setAttribute('index', i.toString()); + } + // How many new words will be added + const newWordsCount = upcomingWords.length - this.typewriterItemIndex; + + // For each stack, without exceeding 500ms in total + // we're setting each words delay time according to the count of them. + const stackTime = Config.getInstance().config.typewriterStackTime ?? 100; + const maxWordTime = Config.getInstance().config.typewriterMaxWordTime ?? 20; + const disableAnimation = Config.getInstance().config.disableTypewriterAnimation ?? false; + const shouldAnimate = !disableAnimation && this.isStreamActive; + const timeForEach = shouldAnimate ? Math.min(maxWordTime, Math.floor(stackTime / newWordsCount)) : 0; + + // Generate animator style and inject into render + // CSS animations ~100 times faster then js timeouts/intervals + if (shouldAnimate) { + newCardContent.render.insertAdjacentElement('beforeend', + getTypewriterPartsCss(this.typewriterId, this.typewriterItemIndex, upcomingWords.length, timeForEach)); + } + + this.props.onAnimationStateChange?.(shouldAnimate); + if (this.contentBody == null) { + this.contentBody = newCardContent; + this.render = this.contentBody.render; + } + this.updateDOMContent(newCardContent); + this.lastAnimationDuration = shouldAnimate ? timeForEach * newWordsCount : 0; + + // If there is another set + // call the same function to check after current stack totally shown + this.updateTimer = setTimeout(() => { + this.updateTimer = undefined; + // Only signal animation end if no more updates are queued + this.props.onAnimationStateChange?.(this.updateStack.length > 0); + this.updateCard(); + }, this.lastAnimationDuration); + } + } + }; + + public readonly updateCardStack = (updateWith: Partial): void => { + this.updateStack.push(updateWith); + this.updateCard(); + }; + + public readonly endStream = (): void => { + this.isStreamActive = false; + this.flushRemainingUpdates(); + }; + + private readonly updateDOMContent = (newCardContent: CardBody): void => { + const upcomingWords = Array.from(newCardContent.render.querySelectorAll('.typewriter-part') ?? []); + for (let i = 0; i < upcomingWords.length; i++) { + upcomingWords[i].setAttribute('index', i.toString()); + } + + if (this.contentBody == null) { + this.contentBody = newCardContent; + this.render = this.contentBody.render; + } + Array.from(newCardContent.render.childNodes).forEach(node => { + const newElm = node as HTMLElement; + const currIndex = (node as HTMLElement).getAttribute('render-index'); + const oldElm = this.render.querySelector(`[render-index="${currIndex ?? ''}"]`); + if (oldElm == null) { + this.render.insertChild('beforeend', node as HTMLElement); + } else if (newElm.innerHTML !== oldElm.innerHTML) { + if (newElm.classList.contains('mynah-syntax-highlighter')) { + const newPreElm = newElm.querySelector('pre'); + if (newPreElm?.childNodes != null) { + const oldElmPre = oldElm.querySelector('pre'); + if (oldElmPre != null) { + oldElmPre.replaceChildren(...Array.from(newPreElm.childNodes)); + if (!newElm.classList.contains('mynah-inline-code') && !newElm.classList.contains('no-max') && oldElmPre.scrollHeight > oldElmPre.clientHeight) { + oldElm.classList.add('max-height-exceed'); + } + } + } + } else { + oldElm.replaceWith(newElm); + } + } + }); + this.contentBody = newCardContent; + this.typewriterItemIndex = upcomingWords.length; + }; + + private readonly flushRemainingUpdates = (): void => { + if (this.updateTimer != null) { + clearTimeout(this.updateTimer); + this.updateTimer = undefined; + } + + // Clean up all animation styles + const existingAnimationStyles = this.render.querySelectorAll(`style[data-typewriter="${this.typewriterId}"], style[type="text/css"]`); + existingAnimationStyles.forEach(style => { + if (style.innerHTML.includes(this.typewriterId)) { + style.remove(); + } + }); + + // Batch all updates into final props + if (this.updateStack.length > 0) { + const finalProps = this.updateStack.reduce((acc, update) => ({ ...acc, ...update }), this.props); + this.updateStack.length = 0; // Clear array efficiently + + this.props = finalProps; + const newCardContent = this.getCardContent(); + this.updateDOMContent(newCardContent); + } + + this.props.onAnimationStateChange?.(false); + }; + + public readonly getRenderDetails = (): CardRenderDetails => { + return { + totalNumberOfCodeBlocks: (this.contentBody?.nextCodeBlockIndex ?? 0) + }; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-card.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-card.ts new file mode 100644 index 00000000..d68e0835 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-card.ts @@ -0,0 +1,1092 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItemBodyRenderer, DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent, MynahUIGlobalEvents } from '../../helper/events'; +import { MynahUITabsStore } from '../../helper/tabs-store'; +import { CardRenderDetails, ChatItem, ChatItemType, MynahEventNames } from '../../static'; +import { CardBody, CardBodyProps } from '../card/card-body'; +import { Icon, MynahIcons } from '../icon'; +import { ChatItemFollowUpContainer } from './chat-item-followup'; +import { ChatItemSourceLinksContainer } from './chat-item-source-links'; +import { ChatItemRelevanceVote } from './chat-item-relevance-vote'; +import { ChatItemTreeViewWrapper } from './chat-item-tree-view-wrapper'; +import { Config } from '../../helper/config'; +import { ChatItemFormItemsWrapper } from './chat-item-form-items'; +import { ChatItemButtonsWrapper, ChatItemButtonsWrapperProps } from './chat-item-buttons'; +import { cleanHtml } from '../../helper/sanitize'; +import { chatItemHasContent } from '../../helper/chat-item'; +import { Card } from '../card/card'; +import { ChatItemCardContent, ChatItemCardContentProps } from './chat-item-card-content'; +import testIds from '../../helper/test-ids'; +import { ChatItemInformationCard } from './chat-item-information-card'; +import { ChatItemTabbedCard } from './chat-item-tabbed-card'; +import { MoreContentIndicator } from '../more-content-indicator'; +import { Button } from '../button'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; +import { marked } from 'marked'; +import { parseMarkdown } from '../../helper/marked'; +import { DropdownWrapper } from '../dropdown-form/dropdown-wrapper'; + +const TOOLTIP_DELAY = 350; +export interface ChatItemCardProps { + tabId: string; + initVisibility?: boolean; + chatItem: ChatItem; + inline?: boolean; + small?: boolean; + onAnimationStateChange?: (isAnimating: boolean) => void; +} +export class ChatItemCard { + readonly props: ChatItemCardProps; + render: ExtendedHTMLElement; + private tooltipOverlay: Overlay | null; + private tooltipTimeout: ReturnType; + private readonly card: Card | null = null; + private readonly updateStack: Array> = []; + private readonly initialSpinner: ExtendedHTMLElement[] | null = null; + private cardFooter: ExtendedHTMLElement | null = null; + private cardHeader: ExtendedHTMLElement | null = null; + private cardTitle: ExtendedHTMLElement | null = null; + private informationCard: ChatItemInformationCard | null = null; + private summary: { + wrapper: ExtendedHTMLElement; + visibleContent: ChatItemCard; + collapsedContent: ExtendedHTMLElement; + stateIcon: Icon; + showSummary: boolean; + } | null = null; + + private tabbedCard: ChatItemTabbedCard | null = null; + private cardIcon: Icon | null = null; + private contentBody: ChatItemCardContent | null = null; + private chatAvatar: ExtendedHTMLElement; + private chatFormItems: ChatItemFormItemsWrapper | null = null; + private customRendererWrapper: CardBody | null = null; + private chatButtonsInside: ChatItemButtonsWrapper | null = null; + private chatButtonsOutside: ChatItemButtonsWrapper | null = null; + private fileTreeWrapper: ChatItemTreeViewWrapper | null = null; + private fileTreeWrapperCollapsedState: boolean | null = null; + private followUps: ChatItemFollowUpContainer | null = null; + private readonly moreContentIndicator: MoreContentIndicator | null = null; + private isMoreContentExpanded: boolean = false; + private votes: ChatItemRelevanceVote | null = null; + private footer: ChatItemCard | null = null; + private header: ChatItemCard | null = null; + constructor (props: ChatItemCardProps) { + this.props = { + ...props, + chatItem: { + ...props.chatItem, + fullWidth: props.chatItem.fullWidth, + padding: props.chatItem.padding != null ? props.chatItem.padding : (props.chatItem.type !== ChatItemType.DIRECTIVE), + } + }; + this.chatAvatar = this.getChatAvatar(); + MynahUITabsStore.getInstance() + .getTabDataStore(this.props.tabId) + .subscribe('showChatAvatars', (value: boolean) => { + if (value && this.canShowAvatar()) { + this.chatAvatar = this.getChatAvatar(); + this.render.insertChild('afterbegin', this.chatAvatar); + } else { + this.chatAvatar.remove(); + } + }); + if (this.props.chatItem.type === ChatItemType.ANSWER_STREAM) { + this.initialSpinner = [ + DomBuilder.getInstance().build({ + type: 'div', + persistent: true, + classNames: [ 'mynah-chat-items-spinner', 'text-shimmer' ], + children: [ { type: 'div', children: [ Config.getInstance().config.texts.spinnerText ] } ], + }), + + ]; + } + + this.cardTitle = this.getCardTitle(); + this.cardHeader = this.getCardHeader(); + this.cardFooter = this.getCardFooter(); + this.card = new Card({ + testId: testIds.chatItem.card, + children: this.initialSpinner ?? [], + background: this.props.inline !== true && this.props.chatItem.type !== ChatItemType.DIRECTIVE && !(this.props.chatItem.fullWidth !== true && (this.props.chatItem.type === ChatItemType.ANSWER || this.props.chatItem.type === ChatItemType.ANSWER_STREAM)), + border: (this.props.inline !== true && + this.props.chatItem.type !== ChatItemType.DIRECTIVE && + !(this.props.chatItem.fullWidth !== true && + (this.props.chatItem.type === ChatItemType.ANSWER || + this.props.chatItem.type === ChatItemType.ANSWER_STREAM))) || + // Auto border for warning/info headers + (this.props.chatItem.border === true), + padding: ((this.props.inline === true || this.props.chatItem.padding === false || (this.props.chatItem.fullWidth !== true && (this.props.chatItem.type === ChatItemType.ANSWER || this.props.chatItem.type === ChatItemType.ANSWER_STREAM))) && + // Exception: warning/info headers should have padding + !(this.props.chatItem.header?.padding === true)) + ? 'none' + : undefined, + }); + this.updateCardContent(); + this.render = this.generateCard(); + + /** + * Generate/update more content indicator if available + */ + this.moreContentIndicator = new MoreContentIndicator({ + icon: MynahIcons.DOWN_OPEN, + border: false, + onClick: () => { + if (this.isMoreContentExpanded) { + this.isMoreContentExpanded = false; + this.render.addClass('mynah-chat-item-collapsed'); + this.moreContentIndicator?.update({ icon: MynahIcons.DOWN_OPEN }); + } else { + this.isMoreContentExpanded = true; + this.render.removeClass('mynah-chat-item-collapsed'); + this.moreContentIndicator?.update({ icon: MynahIcons.UP_OPEN }); + } + }, + testId: testIds.chatItem.moreContentIndicator + }); + this.render.insertChild('beforeend', this.moreContentIndicator.render); + + if (this.props.chatItem.autoCollapse === true) { + this.render.addClass('mynah-chat-item-collapsed'); + } + + if (this.props.chatItem.type === ChatItemType.ANSWER_STREAM && + (this.props.chatItem.body ?? '').trim() !== '') { + this.updateCardStack({}); + } + } + + private readonly getCardFooter = (): ExtendedHTMLElement => { + return DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-footer', 'mynah-card-inner-order-70' ] + }); + }; + + private readonly getCardHeader = (): ExtendedHTMLElement => { + return DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-header', 'mynah-card-inner-order-5' ] + }); + }; + + private readonly getCardTitle = (): ExtendedHTMLElement => { + return DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-title', 'mynah-card-inner-order-3' ] + }); + }; + + private readonly generateCard = (): ExtendedHTMLElement => { + const generatedCard = DomBuilder.getInstance().build({ + type: 'div', + testId: `${testIds.chatItem.type.any}-${this.props.chatItem.type ?? ChatItemType.ANSWER}`, + classNames: this.getCardClasses(), + attributes: { + messageId: this.props.chatItem.messageId ?? 'unknown', + }, + children: [ + ...(this.canShowAvatar() && MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('showChatAvatars') === true ? [ this.chatAvatar ] : []), + ...(this.card != null ? [ this.card?.render ] : []), + ...(this.chatButtonsOutside != null ? [ this.chatButtonsOutside?.render ] : []), + ...(this.props.chatItem.followUp?.text !== undefined ? [ new ChatItemFollowUpContainer({ tabId: this.props.tabId, chatItem: this.props.chatItem }).render ] : []), + ], + }); + + setTimeout( + () => { + this.setMaxHeightClass(this.card?.render); + generatedCard.addClass('reveal'); + }, + 50 + ); + + return generatedCard; + }; + + private readonly setMaxHeightClass = (elm?: ExtendedHTMLElement): void => { + if (elm != null) { + if (this.props.chatItem.autoCollapse === true && elm.scrollHeight > window.innerHeight / 4) { + this.render?.addClass('mynah-chat-item-auto-collapse'); + } else { + this.render?.removeClass('mynah-chat-item-auto-collapse'); + } + } + }; + + private readonly getCardClasses = (): string[] => { + return [ + ...(this.props.chatItem.hoverEffect !== undefined ? [ 'mynah-chat-item-hover-effect' ] : []), + ...(this.props.chatItem.shimmer === true ? [ 'text-shimmer' ] : []), + ...(this.props.chatItem.icon !== undefined ? [ 'mynah-chat-item-card-has-icon' ] : []), + ...(this.props.chatItem.fullWidth === true || this.props.chatItem.type === ChatItemType.ANSWER || this.props.chatItem.type === ChatItemType.ANSWER_STREAM ? [ 'full-width' ] : []), + ...(this.props.chatItem.padding === false ? [ 'no-padding' ] : []), + + ...(this.props.inline === true ? [ 'mynah-ui-chat-item-inline-card' ] : []), + ...(this.props.chatItem.muted === true ? [ 'muted' ] : []), + ...(this.props.small === true ? [ 'mynah-ui-chat-item-small-card' ] : []), + ...(this.props.initVisibility === true ? [ 'reveal' ] : []), + `mynah-chat-item-card-status-${this.props.chatItem.status ?? 'default'}`, + `mynah-chat-item-card-content-horizontal-align-${this.props.chatItem.contentHorizontalAlignment ?? 'default'}`, + 'mynah-chat-item-card', + `mynah-chat-item-${this.props.chatItem.type ?? ChatItemType.ANSWER}`, + ...(!chatItemHasContent(this.props.chatItem) ? [ 'mynah-chat-item-empty' ] : []), + ]; + }; + + private readonly getFilePillsCustomRenderer = (): ChatItem['customRenderer'] => { + const header = this.props.chatItem.header; + if ((header?.fileList) == null) return []; + + const customRenderer: ChatItemBodyRenderer[] = []; + + // Add icon if present + if (header.icon != null) { + customRenderer.push({ + type: 'i' as const, + classNames: [ + 'mynah-ui-icon', + `mynah-ui-icon-${header.icon as MynahIcons}`, + 'mynah-chat-item-card-icon-inline', + `icon-status-${header.iconStatus ?? 'none'}` + ] + }); + } + + // Add body text if present + if (header.body != null && header.body !== '') { + // Parse markdown to handle inline code + const parsedHtml = parseMarkdown(header.body, { includeLineBreaks: true }); + + // Create a temporary div to extract text content while preserving inline code + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = parsedHtml; + + // Convert to ChatItemBodyRenderer format + const processNode = (node: Node): Array => { + if (node.nodeType === Node.TEXT_NODE) { + return [ node.textContent ?? '' ]; + } else if (node.nodeType === Node.ELEMENT_NODE) { + const element = node as HTMLElement; + if (element.tagName.toLowerCase() === 'code') { + return [ { + type: 'code' as const, + classNames: [ 'mynah-syntax-highlighter', 'mynah-inline-code' ], + children: [ element.textContent ?? '' ] + } ]; + } else { + // For other elements, process their children + const children: Array = []; + Array.from(element.childNodes).forEach(child => { + children.push(...processNode(child)); + }); + return children; + } + } + return [ '' ]; + }; + + const children: Array = []; + Array.from(tempDiv.childNodes).forEach(node => { + children.push(...processNode(node)); + }); + + customRenderer.push({ + type: 'span' as const, + children + }); + } + + // Add file pills + const filePills = header.fileList.filePaths?.map(filePath => { + const fileName = header.fileList?.details?.[filePath]?.visibleName ?? filePath; + const isDeleted = header.fileList?.deletedFiles?.includes(filePath) === true; + const description = header.fileList?.details?.[filePath]?.description; + + return { + type: 'span' as const, + classNames: [ + 'mynah-chat-item-tree-file-pill', + ...(isDeleted ? [ 'mynah-chat-item-tree-file-pill-deleted' ] : []) + ], + children: [ fileName ], + events: { + click: () => { + if (header.fileList?.details?.[filePath]?.clickable === false) { + return; + } + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FILE_CLICK, { + tabId: this.props.tabId, + messageId: this.props.chatItem.messageId, + filePath, + deleted: isDeleted + }); + }, + ...(description !== undefined + ? { + mouseover: (e: MouseEvent) => { + this.showTooltip(description, e.target as HTMLElement); + }, + mouseleave: () => { + this.hideTooltip(); + } + } + : {}) + }, + }; + }) ?? []; + + customRenderer.push(...filePills); + + return customRenderer; + }; + + private readonly updateCardContent = (): void => { + if (MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId) === undefined) { + return; + } + + const bodyEvents: Partial = { + onLinkClick: (url: string, e: MouseEvent) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.LINK_CLICK, { + messageId: this.props.chatItem.messageId, + link: url, + event: e, + }); + }, + onCopiedToClipboard: (type, text, referenceTrackerInformation, codeBlockIndex) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.COPY_CODE_TO_CLIPBOARD, { + messageId: this.props.chatItem.messageId, + type, + text, + referenceTrackerInformation, + codeBlockIndex, + totalCodeBlocks: (this.contentBody?.getRenderDetails().totalNumberOfCodeBlocks ?? 0) + (this.customRendererWrapper?.nextCodeBlockIndex ?? 0), + }); + }, + ...(Object.keys(Config.getInstance().config.codeBlockActions ?? {}).length > 0 || Object.keys(this.props.chatItem.codeBlockActions ?? {}).length > 0 + ? { + codeBlockActions: { + ...Config.getInstance().config.codeBlockActions, + ...this.props.chatItem.codeBlockActions + }, + onCodeBlockAction: (actionId, data, type, text, referenceTrackerInformation, codeBlockIndex) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CODE_BLOCK_ACTION, { + actionId, + data, + messageId: this.props.chatItem.messageId, + type, + text, + referenceTrackerInformation, + codeBlockIndex, + totalCodeBlocks: (this.contentBody?.getRenderDetails().totalNumberOfCodeBlocks ?? 0) + (this.customRendererWrapper?.nextCodeBlockIndex ?? 0), + }); + } + } + : {}) + }; + + if (chatItemHasContent(this.props.chatItem)) { + this.initialSpinner?.[0]?.remove(); + } + + // If no data is provided for the header + // skip removing and checking it + if (this.props.chatItem.canBeDismissed === true || this.props.chatItem.title != null) { + if (this.cardTitle != null) { + this.cardTitle.remove(); + this.cardTitle = null; + } + this.cardTitle = this.getCardTitle(); + if (this.props.chatItem.title != null) { + this.cardTitle?.insertChild('beforeend', DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-title-text' ], + children: [ this.props.chatItem.title ] + })); + } + + if (this.props.chatItem.canBeDismissed === true) { + this.cardTitle?.insertChild('beforeend', new Button({ + icon: new Icon({ icon: 'cancel' }).render, + onClick: () => { + this.render.remove(); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CARD_DISMISS, { tabId: this.props.tabId, messageId: this.props.chatItem.messageId }); + if (this.props.chatItem.messageId !== undefined) { + const currentChatItems: ChatItem[] = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('chatItems'); + MynahUITabsStore.getInstance() + .getTabDataStore(this.props.tabId) + .updateStore( + { + chatItems: [ ...currentChatItems.map(chatItem => this.props.chatItem.messageId !== chatItem.messageId ? chatItem : { type: ChatItemType.ANSWER, messageId: chatItem.messageId }) ], + }, + true + ); + } + }, + primary: false, + status: 'clear', + testId: testIds.chatItem.dismissButton + }).render); + } + this.card?.render.insertChild('afterbegin', this.cardTitle); + } + + // Handle data updates with the update structure of the chat item itself + if (this.props.chatItem.header === null) { + this.cardHeader?.remove(); + this.cardHeader = null; + this.header?.render.remove(); + this.header = null; + } else if (this.props.chatItem.header != null) { + if (this.cardHeader != null && this.header != null) { + if (this.props.chatItem.header.fileList?.renderAsPills === true) { + this.cardHeader?.remove(); + this.cardHeader = this.getCardHeader(); + this.card?.render.insertChild('beforeend', this.cardHeader); + this.header = null; + } else { + this.header.updateCardStack({ + ...this.props.chatItem.header, + status: undefined, + type: ChatItemType.ANSWER, + messageId: this.props.chatItem.messageId, + } satisfies ChatItem); + } + } + if (this.header === null) { + this.cardHeader?.remove(); + this.cardHeader = this.getCardHeader(); + this.card?.render.insertChild('beforeend', this.cardHeader); + + this.header = new ChatItemCard({ + tabId: this.props.tabId, + small: true, + initVisibility: true, + inline: true, + chatItem: { + ...this.props.chatItem.header, + status: undefined, + type: ChatItemType.ANSWER, + messageId: this.props.chatItem.messageId, + ...(this.props.chatItem.header.fileList?.renderAsPills === true + ? { + customRenderer: this.getFilePillsCustomRenderer(), + body: null, + fileList: null, + icon: undefined + } + : {}) + }, + }); + this.cardHeader.insertChild('beforeend', this.header.render); + } + + if (this.props.chatItem.header.status != null) { + // Remove existing status before adding new one + this.cardHeader?.querySelector('.mynah-chat-item-card-header-status')?.remove(); + this.cardHeader?.insertAdjacentElement(this.props.chatItem.header.status.position === 'left' ? 'afterbegin' : 'beforeend', DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'mynah-chat-item-card-header-status', `status-${this.props.chatItem.header.status.status ?? 'default'}` ], + children: [ + ...(this.props.chatItem.header.status.icon != null ? [ new Icon({ icon: this.props.chatItem.header.status.icon }).render ] : []), + ...(this.props.chatItem.header.status.text != null ? [ { type: 'span', classNames: [ 'mynah-chat-item-card-header-status-text' ], children: [ this.props.chatItem.header.status.text ] } ] : []), + ], + ...(this.props.chatItem.header.status?.description != null + ? { + events: { + mouseover: (e) => { + cancelEvent(e); + const tooltipText = marked(this.props.chatItem?.header?.status?.description ?? '', { breaks: true }) as string; + this.showTooltip(tooltipText, e.target ?? e.currentTarget); + }, + mouseleave: this.hideTooltip + } + } + : {}) + })); + } + } + + /** + * Generate card icon if available + */ + if (this.props.chatItem.icon != null) { + if (this.cardIcon != null) { + this.cardIcon.render.remove(); + this.cardIcon = null; + } + this.cardIcon = new Icon({ icon: this.props.chatItem.icon, status: this.props.chatItem.iconForegroundStatus, subtract: this.props.chatItem.iconStatus != null, classNames: [ 'mynah-chat-item-card-icon', 'mynah-card-inner-order-10', `icon-status-${this.props.chatItem.iconStatus ?? 'none'}` ] }); + this.card?.render.insertChild('beforeend', this.cardIcon.render); + } + + /** + * Generate contentBody if available + */ + if (this.props.chatItem.body != null && this.props.chatItem.body !== '') { + const updatedCardContentBodyProps: ChatItemCardContentProps = { + body: this.props.chatItem.body ?? '', + hideCodeBlockLanguage: this.props.chatItem.padding === false, + wrapCode: this.props.chatItem.wrapCodes, + unlimitedCodeBlockHeight: this.props.chatItem.autoCollapse, + classNames: [ 'mynah-card-inner-order-20' ], + renderAsStream: this.props.chatItem.type === ChatItemType.ANSWER_STREAM || this.props.chatItem.type === ChatItemType.DIRECTIVE, + codeReference: this.props.chatItem.codeReference ?? undefined, + onAnimationStateChange: (isAnimating) => { + if (isAnimating) { + this.render?.addClass('typewriter-animating'); + } else { + this.render?.removeClass('typewriter-animating'); + this.props.onAnimationStateChange?.(isAnimating); + } + }, + children: + this.props.chatItem.relatedContent !== undefined + ? [ + new ChatItemSourceLinksContainer({ + messageId: this.props.chatItem.messageId ?? 'unknown', + tabId: this.props.tabId, + relatedContent: this.props.chatItem.relatedContent?.content, + title: this.props.chatItem.relatedContent?.title, + }).render, + ] + : [], + contentProperties: bodyEvents, + }; + if (this.contentBody != null) { + this.contentBody.updateCardStack(updatedCardContentBodyProps); + } else { + this.contentBody = new ChatItemCardContent(updatedCardContentBodyProps); + this.card?.render.insertChild('beforeend', this.contentBody.render); + } + } else if (this.props.chatItem.body === null) { + this.contentBody?.render.remove(); + this.contentBody = null; + } + + /** + * Generate customRenderer if available + */ + if (this.customRendererWrapper != null) { + this.customRendererWrapper.render.remove(); + this.customRendererWrapper = null; + } + if (this.props.chatItem.customRenderer != null) { + const customRendererContent: Partial = {}; + + if (typeof this.props.chatItem.customRenderer === 'object') { + customRendererContent.children = Array.isArray(this.props.chatItem.customRenderer) ? this.props.chatItem.customRenderer : [ this.props.chatItem.customRenderer ]; + } else if (typeof this.props.chatItem.customRenderer === 'string') { + customRendererContent.innerHTML = cleanHtml(this.props.chatItem.customRenderer); + } + + this.customRendererWrapper = new CardBody({ + body: customRendererContent.innerHTML, + children: customRendererContent.children, + classNames: [ 'mynah-card-inner-order-30' ], + processChildren: true, + useParts: true, + codeBlockStartIndex: (this.contentBody?.getRenderDetails().totalNumberOfCodeBlocks ?? 0), + ...bodyEvents, + }); + + this.card?.render.insertChild('beforeend', this.customRendererWrapper.render); + } + + /** + * Generate form items if available + */ + if (this.chatFormItems != null) { + this.chatFormItems.render.remove(); + this.chatFormItems = null; + } + if (this.props.chatItem.formItems != null) { + this.chatFormItems = new ChatItemFormItemsWrapper({ + classNames: [ 'mynah-card-inner-order-40' ], + tabId: this.props.tabId, + chatItem: this.props.chatItem, + onModifierEnterPress (formData, tabId) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FORM_MODIFIER_ENTER_PRESS, { formData, tabId }); + }, + onTextualItemKeyPress (event, itemId, formData, tabId, disableAllCallback) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FORM_TEXTUAL_ITEM_KEYPRESS, { + event, + formData, + itemId, + tabId, + callback: (disableAll?: boolean) => { + if (disableAll === true) { + disableAllCallback(); + } + } + }); + }, + onFormChange (formData, isValid, tabId) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FORM_CHANGE, { formData, isValid, tabId }); + }, + }); + this.card?.render.insertChild('beforeend', this.chatFormItems.render); + } + + /** + * Generate file tree if available + */ + if (this.fileTreeWrapper != null) { + this.fileTreeWrapper.render.remove(); + this.fileTreeWrapper = null; + } + if (this.props.chatItem.fileList != null && this.props.chatItem.header?.fileList?.renderAsPills !== true) { + const { filePaths = [], deletedFiles = [], actions, details, flatList } = this.props.chatItem.fileList; + const referenceSuggestionLabel = this.props.chatItem.body ?? ''; + this.fileTreeWrapper = new ChatItemTreeViewWrapper({ + tabId: this.props.tabId, + classNames: [ 'mynah-card-inner-order-50' ], + messageId: this.props.chatItem.messageId ?? '', + cardTitle: this.props.chatItem.fileList.fileTreeTitle, + rootTitle: this.props.chatItem.fileList.rootFolderTitle, + rootStatusIcon: this.props.chatItem.fileList.rootFolderStatusIcon, + rootIconForegroundStatus: this.props.chatItem.fileList.rootFolderStatusIconForegroundStatus, + rootLabel: this.props.chatItem.fileList.rootFolderLabel, + folderIcon: this.props.chatItem.fileList.folderIcon, + hideFileCount: this.props.chatItem.fileList.hideFileCount ?? false, + collapsed: this.fileTreeWrapperCollapsedState != null ? this.fileTreeWrapperCollapsedState : this.props.chatItem.fileList.collapsed != null ? this.props.chatItem.fileList.collapsed : false, + files: filePaths, + deletedFiles, + flatList, + actions, + details, + references: this.props.chatItem.codeReference ?? [], + referenceSuggestionLabel, + onRootCollapsedStateChange: (isRootCollapsed) => { + this.fileTreeWrapperCollapsedState = isRootCollapsed; + } + }); + this.card?.render.insertChild('beforeend', this.fileTreeWrapper.render); + } else { + this.fileTreeWrapperCollapsedState = null; + } + + /** + * Generate information card if available + */ + if (this.informationCard != null) { + this.informationCard.render.remove(); + this.informationCard = null; + } + if (this.props.chatItem.informationCard != null) { + this.informationCard = new ChatItemInformationCard({ + tabId: this.props.tabId, + messageId: this.props.chatItem.messageId, + classNames: [ 'mynah-card-inner-order-55' ], + informationCard: this.props.chatItem.informationCard ?? {} + }); + this.card?.render.insertChild('beforeend', this.informationCard.render); + } + + /** + * Generate summary content if available + */ + if (this.props.chatItem.summary === null && this.summary != null) { + this.summary.stateIcon?.render.remove(); + this.summary.collapsedContent.remove(); + this.summary.visibleContent.render.remove(); + this.summary.wrapper.remove(); + this.summary = null; + } + if (this.props.chatItem.summary != null) { + if (this.summary === null) { + const showSummary = !(this.props.chatItem.summary.isCollapsed !== false); + const collapsedContent = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-summary-collapsed-content' ], + children: [] + }); + const visibleContent = new ChatItemCard({ + tabId: this.props.tabId, + chatItem: { + type: ChatItemType.ANSWER, + ...this.props.chatItem.summary.content, + messageId: this.props.chatItem.messageId, + } + }); + const stateIcon = new Icon({ icon: showSummary ? 'down-open' : 'right-open' }); + this.summary = { + showSummary, + stateIcon, + collapsedContent, + visibleContent, + wrapper: DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-summary', 'mynah-card-inner-order-65', ...(showSummary ? [ 'show-summary' ] : []) ], + children: [ + { + type: 'div', + classNames: [ 'mynah-chat-item-card-summary-content' ], + children: [ + new Button({ + classNames: [ 'mynah-chat-item-summary-button' ], + status: 'clear', + primary: false, + onClick: () => { + if (this.summary != null) { + this.summary.showSummary = !this.summary.showSummary; + if (this.summary.showSummary) { + this.summary.wrapper.addClass('show-summary'); + this.summary.stateIcon?.update('down-open'); + } else { + this.summary.wrapper.removeClass('show-summary'); + this.summary.stateIcon?.update('right-open'); + } + } + }, + icon: stateIcon.render + }).render, + visibleContent.render + ] + }, + collapsedContent + ] + }) + }; + } + + if (this.props.chatItem.summary.content != null) { + this.summary.visibleContent.updateCardStack(this.props.chatItem.summary.content); + } + if (this.props.chatItem.summary.collapsedContent != null) { + this.summary.collapsedContent.update({ + children: this.props.chatItem.summary.collapsedContent?.map(summaryChatItem => new ChatItemCard({ + tabId: this.props.tabId, + chatItem: { + type: ChatItemType.ANSWER, + ...summaryChatItem, + messageId: this.props.chatItem.messageId, + } + }).render) + }); + } + if (this.props.chatItem.summary.isCollapsed != null) { + this.summary.showSummary = !this.props.chatItem.summary.isCollapsed; + if (this.summary.showSummary) { + this.summary.wrapper.addClass('show-summary'); + this.summary.stateIcon?.update('down-open'); + } else { + this.summary.wrapper.removeClass('show-summary'); + this.summary.stateIcon?.update('right-open'); + } + } + + this.card?.render.insertChild('beforeend', this.summary.wrapper); + } + + /** + * Generate buttons if available + */ + if (this.chatButtonsInside != null) { + this.chatButtonsInside.render.remove(); + this.chatButtonsInside = null; + } + if (this.chatButtonsOutside != null) { + this.chatButtonsOutside.render.remove(); + this.chatButtonsOutside = null; + } + if (this.props.chatItem.buttons != null) { + const insideButtons = this.props.chatItem.buttons.filter((button) => button.position == null || button.position === 'inside'); + const outsideButtons = this.props.chatItem.buttons.filter((button) => button.position === 'outside'); + + const chatButtonProps: ChatItemButtonsWrapperProps = { + tabId: this.props.tabId, + classNames: [ 'mynah-card-inner-order-60' ], + formItems: this.chatFormItems, + buttons: [], + onActionClick: action => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.BODY_ACTION_CLICKED, { + tabId: this.props.tabId, + messageId: this.props.chatItem.messageId, + actionId: action.id, + actionText: action.text, + ...(this.chatFormItems !== null ? { formItemValues: this.chatFormItems.getAllValues() } : {}), + }); + + if (action.keepCardAfterClick === false) { + this.render.remove(); + if (this.props.chatItem.messageId !== undefined) { + const currentChatItems: ChatItem[] = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('chatItems'); + MynahUITabsStore.getInstance() + .getTabDataStore(this.props.tabId) + .updateStore( + { + chatItems: [ ...currentChatItems.map(chatItem => this.props.chatItem.messageId !== chatItem.messageId ? chatItem : { type: ChatItemType.ANSWER, messageId: chatItem.messageId }) ], + }, + true + ); + } + } + }, + }; + + if (insideButtons.length > 0) { + this.chatButtonsInside = new ChatItemButtonsWrapper({ ...chatButtonProps, buttons: insideButtons }); + this.card?.render.insertChild('beforeend', this.chatButtonsInside.render); + } + if (outsideButtons.length > 0) { + this.chatButtonsOutside = new ChatItemButtonsWrapper({ ...chatButtonProps, buttons: outsideButtons }); + this.render?.insertChild('beforeend', this.chatButtonsOutside.render); + } + } + + /** + * Generate tabbed card if available + */ + if (this.tabbedCard != null) { + this.tabbedCard.render.remove(); + this.tabbedCard = null; + } + if (this.props.chatItem.tabbedContent != null) { + this.tabbedCard = new ChatItemTabbedCard({ + tabId: this.props.tabId, + messageId: this.props.chatItem.messageId, + tabbedCard: this.props.chatItem.tabbedContent, + classNames: [ 'mynah-card-inner-order-55' ] + }); + this.card?.render.insertChild('beforeend', this.tabbedCard.render); + } + + /** + * Clear footer block + */ + if (this.cardFooter != null) { + this.cardFooter.remove(); + this.cardFooter = null; + } + + if (this.props.chatItem.footer != null || this.props.chatItem.canBeVoted === true || this.shouldShowQuickSettings()) { + this.cardFooter = this.getCardFooter(); + this.card?.render.insertChild('beforeend', this.cardFooter); + + /** + * Generate footer if available + */ + if (this.footer != null) { + this.footer.render.remove(); + this.footer = null; + } + if (this.props.chatItem.footer != null) { + this.footer = new ChatItemCard({ + tabId: this.props.tabId, + small: true, + inline: true, + chatItem: { + ...this.props.chatItem.footer, + type: ChatItemType.ANSWER, + messageId: this.props.chatItem.messageId + } + }); + this.cardFooter.insertChild('beforeend', this.footer.render); + } + + /** + * Generate votes if available + */ + if (this.votes !== null) { + this.votes.render.remove(); + this.votes = null; + } + if (this.props.chatItem.canBeVoted === true && this.props.chatItem.messageId !== undefined) { + this.votes = new ChatItemRelevanceVote({ + tabId: this.props.tabId, + messageId: this.props.chatItem.messageId + }); + this.cardFooter.insertChild('beforeend', this.votes.render); + } + + /** + * Add QuickSettings to footer if available + */ + if (this.props.chatItem.quickSettings != null) { + const dropdownContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-dropdown-list-container' ], + children: [ + new DropdownWrapper({ + dropdownProps: this.props.chatItem.quickSettings + }).render + ] + }); + this.cardFooter.insertChild('beforeend', dropdownContainer); + } + } + + /** + * Generate/update followups if available + */ + if (this.followUps !== null) { + this.followUps.render.remove(); + this.followUps = null; + } + if (this.props.chatItem.followUp != null) { + this.followUps = new ChatItemFollowUpContainer({ tabId: this.props.tabId, chatItem: this.props.chatItem }); + this.render?.insertChild('beforeend', this.followUps.render); + } + }; + + private readonly getChatAvatar = (): ExtendedHTMLElement => + DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-icon-wrapper' ], + children: [ new Icon({ icon: this.props.chatItem.type === ChatItemType.PROMPT ? MynahIcons.USER : MynahIcons.Q }).render ], + }); + + private readonly canShowAvatar = (): boolean => (this.props.chatItem.type === ChatItemType.ANSWER_STREAM || (this.props.inline !== true && chatItemHasContent({ ...this.props.chatItem, followUp: undefined }))); + + private readonly shouldShowQuickSettings = (): boolean => { + return this.props.chatItem.quickSettings != null; + }; + + private readonly showTooltip = (content: string, elm: HTMLElement): void => { + if (content.trim() !== undefined) { + clearTimeout(this.tooltipTimeout); + this.tooltipTimeout = setTimeout(() => { + this.tooltipOverlay = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.CENTER, + children: [ + new Card({ + border: false, + children: [ + new CardBody({ + body: content + }).render + ] + }).render + ], + }); + }, TOOLTIP_DELAY); + } + }; + + public readonly hideTooltip = (): void => { + clearTimeout(this.tooltipTimeout); + if (this.tooltipOverlay !== null) { + this.tooltipOverlay?.close(); + this.tooltipOverlay = null; + } + }; + + public readonly updateCard = (): void => { + if (this.updateStack.length > 0) { + const updateWith: Partial | undefined = this.updateStack.shift(); + if (updateWith !== undefined) { + // Update item inside the store + if (this.props.chatItem.messageId != null) { + const currentTabChatItems = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId)?.getStore()?.chatItems; + MynahUITabsStore.getInstance() + .getTabDataStore(this.props.tabId) + .updateStore( + { + chatItems: currentTabChatItems?.map((chatItem: ChatItem) => { + if (chatItem.messageId === this.props.chatItem.messageId) { + return { + ...this.props.chatItem, + ...updateWith + }; + } + return chatItem; + }), + }, + true + ); + } + + this.props.chatItem = { + ...this.props.chatItem, + ...updateWith, + }; + + this.render?.update({ + ...(this.props.chatItem.messageId != null + ? { + attributes: { + messageid: this.props.chatItem.messageId + } + } + : {}), + classNames: [ ...this.getCardClasses(), 'reveal', ...(this.isMoreContentExpanded ? [ ] : [ 'mynah-chat-item-collapsed' ]) ], + }); + this.updateCardContent(); + this.updateCard(); + this.setMaxHeightClass(this.card?.render); + } + } + }; + + public readonly updateCardStack = (updateWith: Partial): void => { + this.updateStack.push(updateWith); + this.updateCard(); + }; + + public readonly clearContent = (): void => { + this.cardHeader?.remove(); + this.cardHeader = null; + + this.contentBody?.render.remove(); + this.contentBody = null; + + this.chatButtonsInside?.render.remove(); + this.chatButtonsInside = null; + + this.customRendererWrapper?.render.remove(); + this.customRendererWrapper = null; + + this.fileTreeWrapper?.render.remove(); + this.fileTreeWrapper = null; + + this.followUps?.render.remove(); + this.followUps = null; + + this.cardFooter?.remove(); + this.cardFooter = null; + + this.chatFormItems?.render.remove(); + this.chatFormItems = null; + + this.informationCard?.render.remove(); + this.informationCard = null; + + this.tabbedCard?.render.remove(); + this.tabbedCard = null; + }; + + public readonly getRenderDetails = (): CardRenderDetails => { + return { + totalNumberOfCodeBlocks: (this.contentBody?.getRenderDetails().totalNumberOfCodeBlocks ?? 0) + (this.customRendererWrapper?.nextCodeBlockIndex ?? 0) + }; + }; + + public readonly endStream = (): void => { + this.contentBody?.endStream(); + }; + + public readonly cleanFollowupsAndRemoveIfEmpty = (): boolean => { + this.followUps?.render?.remove(); + this.followUps = null; + if (!chatItemHasContent({ + ...this.props.chatItem, + followUp: undefined + })) { + this.render.remove(); + return true; + } + return false; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-followup.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-followup.ts new file mode 100644 index 00000000..16df8e07 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-followup.ts @@ -0,0 +1,98 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { MynahUIGlobalEvents } from '../../helper/events'; +import testIds from '../../helper/test-ids'; +import { ChatItem, MynahEventNames } from '../../static'; +import { Button } from '../button'; +import { Icon } from '../icon'; + +export interface ChatItemFollowUpProps {tabId: string; chatItem: ChatItem} +export class ChatItemFollowUpContainer { + private readonly props: ChatItemFollowUpProps; + render: ExtendedHTMLElement; + private readonly itemAddListenerId: string; + private followupOptions: Button[]; + constructor (props: ChatItemFollowUpProps) { + this.props = props; + this.props.chatItem = props.chatItem; + this.followupOptions = (this.props.chatItem.followUp?.options ?? []).map(followUpOption => ( + new Button({ + testId: testIds.chatItem.chatItemFollowup.optionButton, + classNames: [ 'mynah-chat-item-followup-question-option' ], + status: followUpOption.status, + label: followUpOption.pillText, + tooltip: followUpOption.description, + disabled: followUpOption.disabled, + border: true, + primary: false, + ...(followUpOption.icon != null ? { icon: new Icon({ icon: followUpOption.icon }).render } : {}), + onClick: () => { + MynahUIGlobalEvents.getInstance().removeListener(MynahEventNames.CHAT_ITEM_ADD, this.itemAddListenerId); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FOLLOW_UP_CLICKED, { + tabId: this.props.tabId, + messageId: this.props.chatItem.messageId, + followUpOption + }); + if ((this.render.parentElement as ExtendedHTMLElement)?.hasClass('mynah-chat-item-empty')) { + this.render.parentElement?.remove(); + } else { + this.render.remove(); + }; + } + }) + )); + this.itemAddListenerId = MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CHAT_ITEM_ADD, (data) => { + if (data.tabId === this.props.tabId) { + this.render.remove(); + this.followupOptions.forEach(option => option.hideTooltip()); + this.followupOptions = []; + MynahUIGlobalEvents.getInstance().removeListener(MynahEventNames.CHAT_ITEM_ADD, this.itemAddListenerId); + } + }); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.chatItemFollowup.wrapper, + classNames: [ 'mynah-chat-item-followup-question' ], + children: [ + { + type: 'div', + testId: testIds.chatItem.chatItemFollowup.title, + classNames: [ 'mynah-chat-item-followup-question-text' ], + children: [ this.props.chatItem.followUp?.text ?? '' ] + }, + { + type: 'div', + testId: testIds.chatItem.chatItemFollowup.optionsWrapper, + classNames: [ 'mynah-chat-item-followup-question-options-wrapper' ], + children: this.followupOptions.map(option => option.render) + }, + ] + }); + + Array.from(this.render.getElementsByTagName('a')).forEach(a => { + const url = a.href; + + a.onclick = (event: MouseEvent) => { + this.handleLinkClick(url, event); + }; + a.onauxclick = (event: MouseEvent) => { + this.handleLinkClick(url, event); + }; + }); + } + + private readonly handleLinkClick = (url: string, event?: MouseEvent): void => { + MynahUIGlobalEvents + .getInstance() + .dispatch(MynahEventNames.LINK_CLICK, { + tabId: this.props.tabId, + messageId: this.props.chatItem.messageId, + link: url, + event, + }); + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-form-items.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-form-items.ts new file mode 100644 index 00000000..ca23d641 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-form-items.ts @@ -0,0 +1,405 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import testIds from '../../helper/test-ids'; +import { isMandatoryItemValid, isTextualFormItemValid } from '../../helper/validator'; +import { ChatItem, ChatItemFormItem, TextBasedFormItem } from '../../static'; +import { Card } from '../card/card'; +import { CardBody } from '../card/card-body'; +import { Checkbox } from '../form-items/checkbox'; +import { FormItemList } from '../form-items/form-item-list'; +import { FormItemPillBox } from '../form-items/form-item-pill-box'; +import { RadioGroup } from '../form-items/radio-group'; +import { Select } from '../form-items/select'; +import { Stars } from '../form-items/stars'; +import { Switch } from '../form-items/switch'; +import { TextArea } from '../form-items/text-area'; +import { TextInput } from '../form-items/text-input'; +import { Icon, MynahIcons } from '../icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; +const TOOLTIP_DELAY = 350; +export interface ChatItemFormItemsWrapperProps { + tabId: string; + chatItem: Partial; + classNames?: string[]; + onModifierEnterPress?: (formData: Record, tabId: string) => void; + onTextualItemKeyPress?: (event: KeyboardEvent, itemId: string, formData: Record, tabId: string, disableAllCallback: () => void) => void; + onFormChange?: (formData: Record, isValid: boolean, tabId: string) => void; +} +export class ChatItemFormItemsWrapper { + private readonly props: ChatItemFormItemsWrapperProps; + private readonly options: Record = {}; + private readonly validationItems: Record = {}; + private isValid: boolean = false; + private tooltipOverlay: Overlay | null; + private tooltipTimeout: ReturnType; + onValidationChange?: (isValid: boolean) => void; + onAllFormItemsDisabled?: () => void; + + render: ExtendedHTMLElement; + constructor (props: ChatItemFormItemsWrapperProps) { + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.chatItemForm.wrapper, + classNames: [ 'mynah-chat-item-form-items-container', ...(this.props.classNames ?? []) ], + children: this.props.chatItem.formItems?.map(chatItemOption => { + const title = `${chatItemOption.mandatory === true && chatItemOption.hideMandatoryIcon !== true ? '* ' : ''}${chatItemOption.title ?? ''}`; + let chatOption: Select | RadioGroup | TextArea | Stars | TextInput | Checkbox | FormItemList | undefined; + const label: ExtendedHTMLElement = DomBuilder.getInstance().build({ + type: 'div', + children: [ title ] + }); + if (chatItemOption.boldTitle === true) { + label.addClass('.mynah-ui-form-item-bold-label'); + } + if (chatItemOption.mandatory === true) { + if (chatItemOption.hideMandatoryIcon !== true) { + // Add the mandatory class to the existing label + label.addClass('mynah-ui-form-item-mandatory-title'); + label.update({ + testId: testIds.chatItem.chatItemForm.title, + children: [ + new Icon({ icon: MynahIcons.ASTERISK }).render, + chatItemOption.title ?? '', + ] + }); + } + // Since the field is mandatory, default the selected value to the first option + if (chatItemOption.type === 'select' && chatItemOption.value === undefined) { + chatItemOption.value = chatItemOption.options?.[0]?.value; + } + } + let description; + if (chatItemOption.description != null) { + description = DomBuilder.getInstance().build({ + type: 'span', + testId: testIds.chatItem.chatItemForm.description, + classNames: [ 'mynah-ui-form-item-description' ], + children: [ + chatItemOption.description, + ] + }); + } + const fireModifierAndEnterKeyPress = (): void => { + if ((chatItemOption as TextBasedFormItem).checkModifierEnterKeyPress === true && this.isFormValid()) { + this.props.onModifierEnterPress?.(this.getAllValues(), props.tabId); + } + }; + const value = chatItemOption.value?.toString(); + switch (chatItemOption.type) { + case 'list': + chatOption = new FormItemList({ + wrapperTestId: testIds.chatItem.chatItemForm.itemList, + label, + description, + items: chatItemOption.items, + value: chatItemOption.value, + ...(this.getHandlers(chatItemOption)) + }); + if (chatItemOption.disabled === true) { + chatOption.setEnabled(false); + } + break; + case 'select': + chatOption = new Select({ + wrapperTestId: testIds.chatItem.chatItemForm.itemSelectWrapper, + optionTestId: testIds.chatItem.chatItemForm.itemSelect, + label, + border: chatItemOption.border, + autoWidth: chatItemOption.autoWidth, + description, + value, + icon: chatItemOption.icon, + options: chatItemOption.options, + optional: chatItemOption.mandatory !== true, + placeholder: chatItemOption.placeholder ?? Config.getInstance().config.texts.pleaseSelect, + tooltip: chatItemOption.selectTooltip ?? '', + ...(this.getHandlers(chatItemOption)) + }); + if (chatItemOption.disabled === true) { + chatOption.setEnabled(false); + } + break; + case 'radiogroup': + case 'toggle': + chatOption = new RadioGroup({ + type: chatItemOption.type, + wrapperTestId: testIds.chatItem.chatItemForm.itemRadioWrapper, + optionTestId: testIds.chatItem.chatItemForm.itemRadio, + label, + description, + value, + options: chatItemOption.options, + optional: chatItemOption.mandatory !== true, + ...(this.getHandlers(chatItemOption)) + }); + if (chatItemOption.disabled === true) { + chatOption.setEnabled(false); + } + break; + case 'checkbox': + chatOption = new Checkbox({ + wrapperTestId: testIds.chatItem.chatItemForm.itemToggleWrapper, + optionTestId: testIds.chatItem.chatItemForm.itemToggleOption, + title: label, + label: chatItemOption.label, + icon: chatItemOption.icon, + description, + value: value as 'true' | 'false', + optional: chatItemOption.mandatory !== true, + ...(this.getHandlers(chatItemOption)) + }); + break; + case 'switch': + chatOption = new Switch({ + testId: testIds.chatItem.chatItemForm.itemSwitch, + title: label, + label: chatItemOption.label, + icon: chatItemOption.icon, + description, + value: value as 'true' | 'false', + optional: chatItemOption.mandatory !== true, + ...(this.getHandlers(chatItemOption)) + }); + break; + case 'textarea': + chatOption = new TextArea({ + testId: testIds.chatItem.chatItemForm.itemTextArea, + label, + autoFocus: chatItemOption.autoFocus, + description, + fireModifierAndEnterKeyPress, + onKeyPress: (event) => { + this.handleTextualItemKeyPressEvent(event, chatItemOption.id); + }, + value, + mandatory: chatItemOption.mandatory, + validationPatterns: chatItemOption.validationPatterns, + placeholder: chatItemOption.placeholder, + ...(this.getHandlers(chatItemOption)) + }); + break; + case 'textinput': + chatOption = new TextInput({ + testId: testIds.chatItem.chatItemForm.itemInput, + label, + autoFocus: chatItemOption.autoFocus, + description, + icon: chatItemOption.icon, + fireModifierAndEnterKeyPress, + onKeyPress: (event) => { + this.handleTextualItemKeyPressEvent(event, chatItemOption.id); + }, + value, + mandatory: chatItemOption.mandatory, + validationPatterns: chatItemOption.validationPatterns, + validateOnChange: chatItemOption.validateOnChange, + placeholder: chatItemOption.placeholder, + ...(this.getHandlers(chatItemOption)) + }); + if (chatItemOption.disabled === true) { + chatOption.setEnabled(false); + } + break; + case 'numericinput': + chatOption = new TextInput({ + testId: testIds.chatItem.chatItemForm.itemInput, + label, + autoFocus: chatItemOption.autoFocus, + description, + icon: chatItemOption.icon, + fireModifierAndEnterKeyPress, + onKeyPress: (event) => { + this.handleTextualItemKeyPressEvent(event, chatItemOption.id); + }, + value, + mandatory: chatItemOption.mandatory, + validationPatterns: chatItemOption.validationPatterns, + type: 'number', + placeholder: chatItemOption.placeholder, + ...(this.getHandlers(chatItemOption)) + }); + if (chatItemOption.disabled === true) { + chatOption.setEnabled(false); + } + break; + case 'email': + chatOption = new TextInput({ + testId: testIds.chatItem.chatItemForm.itemInput, + label, + autoFocus: chatItemOption.autoFocus, + description, + icon: chatItemOption.icon, + fireModifierAndEnterKeyPress, + onKeyPress: (event) => { + this.handleTextualItemKeyPressEvent(event, chatItemOption.id); + }, + value, + mandatory: chatItemOption.mandatory, + validationPatterns: chatItemOption.validationPatterns, + type: 'email', + placeholder: chatItemOption.placeholder, + ...(this.getHandlers(chatItemOption)) + }); + if (chatItemOption.disabled === true) { + chatOption.setEnabled(false); + } + break; + case 'pillbox': + chatOption = new FormItemPillBox({ + id: chatItemOption.id, + wrapperTestId: testIds.chatItem.chatItemForm.itemInput, + label, + description, + value, + placeholder: chatItemOption.placeholder, + ...(this.getHandlers(chatItemOption)) + }); + break; + case 'stars': + chatOption = new Stars({ + wrapperTestId: testIds.chatItem.chatItemForm.itemStarsWrapper, + optionTestId: testIds.chatItem.chatItemForm.itemStars, + label, + description, + value, + ...(this.getHandlers(chatItemOption)) + }); + break; + default: + break; + } + + if (chatOption != null) { + this.options[chatItemOption.id] = chatOption; + if (chatItemOption.tooltip != null) { + chatOption.render.update({ + events: { + mouseover: (e) => { + cancelEvent(e); + if (chatItemOption.tooltip != null && chatOption?.render != null) { + let tooltipToShow = chatItemOption.tooltip; + if ((chatItemOption.type === 'checkbox' || chatItemOption.type === 'switch') && chatItemOption.alternateTooltip != null && chatOption.getValue() === 'false') { + tooltipToShow = chatItemOption.alternateTooltip; + } + this.showTooltip(tooltipToShow, chatOption.render); + } + }, + mouseleave: this.hideTooltip + } + }); + } + return chatOption.render; + } + return null; + }) as ExtendedHTMLElement[] + }); + this.isFormValid(); + } + + private readonly showTooltip = (content: string, elm: HTMLElement | ExtendedHTMLElement): void => { + if (content.trim() !== undefined) { + clearTimeout(this.tooltipTimeout); + this.tooltipTimeout = setTimeout(() => { + this.tooltipOverlay = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + new Card({ + border: false, + children: [ + new CardBody({ + body: content + }).render + ] + }).render + ], + }); + }, TOOLTIP_DELAY); + } + }; + + public readonly hideTooltip = (): void => { + clearTimeout(this.tooltipTimeout); + if (this.tooltipOverlay !== null) { + this.tooltipOverlay?.close(); + this.tooltipOverlay = null; + } + }; + + private readonly getHandlers = (chatItemOption: ChatItemFormItem): Object => { + if (chatItemOption.mandatory === true || + ([ 'textarea', 'textinput', 'numericinput', 'email', 'pillbox' ].includes(chatItemOption.type) && (chatItemOption as TextBasedFormItem).validationPatterns != null)) { + // Set initial validation status + this.validationItems[chatItemOption.id] = this.isItemValid(chatItemOption.value as string ?? '', chatItemOption); + return { + onChange: (value: string | number) => { + this.props.onFormChange?.(this.getAllValues(), this.isFormValid(), this.props.tabId); + this.validationItems[chatItemOption.id] = this.isItemValid(value.toString(), chatItemOption); + this.isFormValid(); + } + }; + } + return { + onChange: () => { + this.props.onFormChange?.(this.getAllValues(), this.isFormValid(), this.props.tabId); + } + }; + }; + + private readonly handleTextualItemKeyPressEvent = (event: KeyboardEvent, itemId: string): void => { + this.isFormValid() && this.props.onTextualItemKeyPress?.(event, itemId, this.getAllValues(), this.props.tabId, this.disableAll); + }; + + private readonly isItemValid = (value: string, chatItemOption: ChatItemFormItem): boolean => { + let validationState = true; + if (chatItemOption.mandatory === true) { + validationState = isMandatoryItemValid(value ?? ''); + } + if (((chatItemOption.type === 'textarea' || chatItemOption.type === 'textinput') && chatItemOption.validationPatterns != null)) { + validationState = validationState && isTextualFormItemValid(value ?? '', chatItemOption.validationPatterns ?? { patterns: [] }, chatItemOption.mandatory).isValid; + } + + return validationState; + }; + + isFormValid = (): boolean => { + const currentValidationStatus = Object.keys(this.validationItems).reduce((prev, curr) => { + return prev && this.validationItems[curr]; + }, true); + + if (this.isValid !== currentValidationStatus && this.onValidationChange !== undefined) { + this.onValidationChange(currentValidationStatus); + } + this.isValid = currentValidationStatus; + return currentValidationStatus; + }; + + disableAll = (): void => { + Object.keys(this.options).forEach(chatOptionId => this.options[chatOptionId].setEnabled(false)); + this.onAllFormItemsDisabled?.(); + }; + + enableAll = (): void => { + Object.keys(this.options).forEach(chatOptionId => this.options[chatOptionId].setEnabled(true)); + }; + + getAllValues = (): Record>> => { + const valueMap: Record>> = {}; + Object.keys(this.options).forEach(chatOptionId => { + valueMap[chatOptionId] = this.options[chatOptionId].getValue(); + }); + return valueMap; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-information-card.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-information-card.ts new file mode 100644 index 00000000..ae6f7130 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-information-card.ts @@ -0,0 +1,68 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { ChatItemContent, ChatItemType } from '../../static'; +import { ChatItemCard } from './chat-item-card'; +import { TitleDescriptionWithIcon } from '../title-description-with-icon'; +import { StyleLoader } from '../../helper/style-loader'; + +export interface ChatItemInformationCardProps { + tabId: string; + testId?: string; + messageId: string | undefined; + classNames?: string[]; + informationCard: NonNullable['informationCard']>; +} + +export class ChatItemInformationCard { + render: ExtendedHTMLElement; + + constructor (props: ChatItemInformationCardProps) { + StyleLoader.getInstance().load('components/chat/_chat-item-card-information-card.scss'); + + const mainContent = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-information-card-main' ], + children: [ + new TitleDescriptionWithIcon({ + classNames: [ 'mynah-chat-item-information-card-header-container' ], + icon: props.informationCard.icon, + title: props.informationCard.title, + description: props.informationCard.description, + testId: `${props.testId ?? ''}-header`, + }).render + ] + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: props.testId, + classNames: [ 'mynah-chat-item-information-card', ...(props.classNames ?? []), Object.keys(props.informationCard.status ?? {}).length > 0 ? 'has-footer' : '' ], + children: [ + mainContent + ] + }); + + mainContent.insertChild('beforeend', new ChatItemCard({ + tabId: props.tabId, + small: true, + inline: true, + chatItem: { + ...props.informationCard.content, + type: ChatItemType.ANSWER, + messageId: props.messageId, + } + }).render); + + if (props.informationCard.status != null) { + const statusFooter = new TitleDescriptionWithIcon({ + testId: `${props.testId ?? ''}-footer`, + classNames: [ + 'mynah-chat-item-information-card-footer', + ...(props.informationCard.status.status != null ? [ `status-${props.informationCard.status.status}` ] : []), + ], + icon: props.informationCard.status.icon, + description: props.informationCard.status.body + }).render; + this.render.insertChild('beforeend', statusFooter); + } + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-relevance-vote.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-relevance-vote.ts new file mode 100644 index 00000000..692a7722 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-relevance-vote.ts @@ -0,0 +1,126 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { FeedbackPayload, MynahEventNames, RelevancyVoteType } from '../../static'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { Icon, MynahIcons } from '../icon'; +import { MynahUIGlobalEvents } from '../../helper/events'; +import { Button } from '../button'; +import { Config } from '../../helper/config'; +import testIds from '../../helper/test-ids'; + +const THANKS_REMOVAL_DURATION = 3500; +export interface ChatItemRelevanceVoteProps { + tabId: string; + classNames?: string[]; + messageId: string; +} +export class ChatItemRelevanceVote { + private readonly votingId: string; + private sendFeedbackListenerId: string | undefined; + render: ExtendedHTMLElement; + props: ChatItemRelevanceVoteProps; + constructor (props: ChatItemRelevanceVoteProps) { + this.props = props; + this.votingId = `${this.props.tabId}-${this.props.messageId}`; + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-card-votes-wrapper', ...(this.props.classNames ?? []) ], + testId: testIds.chatItem.vote.wrapper, + children: [ + { + type: 'div', + classNames: [ 'mynah-card-vote' ], + children: [ + { + type: 'input', + testId: testIds.chatItem.vote.upvote, + events: { + change: (e: Event) => { + this.handleVoteChange(RelevancyVoteType.UP); + }, + }, + attributes: { + type: 'radio', + id: `${this.votingId}-vote-up`, + name: `${this.votingId}-vote`, + value: 'up', + }, + classNames: [ 'mynah-vote-radio', 'mynah-vote-up-radio' ], + }, + { + type: 'input', + testId: testIds.chatItem.vote.downvote, + events: { + change: (e: Event) => { + this.handleVoteChange(RelevancyVoteType.DOWN); + }, + }, + attributes: { + type: 'radio', + id: `${this.votingId}-vote-down`, + name: `${this.votingId}-vote`, + value: 'down', + }, + classNames: [ 'mynah-vote-radio', 'mynah-vote-down-radio' ], + }, + { + type: 'label', + testId: testIds.chatItem.vote.upvoteLabel, + attributes: { for: `${this.votingId}-vote-up` }, + classNames: [ 'mynah-vote-label', 'mynah-vote-up' ], + children: [ new Icon({ icon: MynahIcons.THUMBS_UP }).render ], + }, + { + type: 'label', + testId: testIds.chatItem.vote.downvoteLabel, + attributes: { for: `${this.votingId}-vote-down` }, + classNames: [ 'mynah-vote-label', 'mynah-vote-down' ], + children: [ new Icon({ icon: MynahIcons.THUMBS_DOWN }).render ], + }, + ], + }, + ], + }); + } + + private readonly handleVoteChange = (vote: RelevancyVoteType): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CARD_VOTE, { messageId: this.props.messageId, tabId: this.props.tabId, vote }); + const newChildren = [ + DomBuilder.getInstance().build({ + type: 'span', + testId: testIds.chatItem.vote.thanks, + innerHTML: Config.getInstance().config.texts.feedbackThanks, + }), + ...(vote === RelevancyVoteType.DOWN + ? [ + new Button({ + testId: testIds.chatItem.vote.reportButton, + label: Config.getInstance().config.texts.feedbackReportButtonLabel, + onClick: () => { + if (this.sendFeedbackListenerId === undefined) { + this.sendFeedbackListenerId = MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FEEDBACK_SET, (data: FeedbackPayload) => { + if (data.messageId === this.props.messageId && data.tabId === this.props.tabId) { + MynahUIGlobalEvents.getInstance().removeListener(MynahEventNames.FEEDBACK_SET, this.sendFeedbackListenerId as string); + this.render.remove(); + } + }); + } + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.SHOW_FEEDBACK_FORM, { tabId: this.props.tabId, messageId: this.props.messageId }); + }, + primary: false + }).render + ] + : []) + ]; + this.render.replaceChildren(...newChildren); + + if (vote === RelevancyVoteType.UP) { + setTimeout(() => { + this.render.remove(); + }, THANKS_REMOVAL_DURATION); + } + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-source-links.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-source-links.ts new file mode 100644 index 00000000..b90b698d --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-source-links.ts @@ -0,0 +1,86 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { MynahUIGlobalEvents } from '../../helper/events'; +import testIds from '../../helper/test-ids'; +import { MynahEventNames, SourceLink } from '../../static'; +import { Button } from '../button'; +import { Card } from '../card/card'; +import { Icon, MynahIcons } from '../icon'; +import { SourceLinkHeader } from '../source-link/source-link-header'; + +const MAX_ITEMS = 1; +export interface ChatItemSourceLinksContainerProps { + tabId: string; + messageId: string; + title?: string; + relatedContent?: SourceLink[]; +} +export class ChatItemSourceLinksContainer { + private readonly props: ChatItemSourceLinksContainerProps; + private readonly showMoreButtonBlock: Button; + render: ExtendedHTMLElement; + chatAvatar: ExtendedHTMLElement; + constructor (props: ChatItemSourceLinksContainerProps) { + this.props = props; + this.showMoreButtonBlock = new Button({ + testId: testIds.chatItem.relatedLinks.showMore, + classNames: [ 'mynah-chat-item-card-related-content-show-more' ], + primary: false, + icon: new Icon({ icon: MynahIcons.DOWN_OPEN }).render, + onClick: () => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.SHOW_MORE_WEB_RESULTS_CLICK, { messageId: this.props.messageId }); + this.showMoreButtonBlock.render.remove(); + (this.render).addClass('expanded'); + }, + label: 'Show more', + }); + + if (this.props.relatedContent !== undefined) { + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.relatedLinks.wrapper, + classNames: [ 'mynah-chat-item-card-related-content', + this.props.relatedContent !== undefined && this.props.relatedContent.length <= MAX_ITEMS ? 'expanded' : '' ], + children: [ + ...(this.props.title !== undefined + ? [ { + type: 'span', + testId: testIds.chatItem.relatedLinks.title, + classNames: [ 'mynah-chat-item-card-related-content-title' ], + children: [ this.props.title ], + } ] + : []), + ...this.props.relatedContent.map(sourceLink => DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-card-related-content-item' ], + children: [ + new Card({ + background: false, + border: false, + padding: 'none', + children: [ + new SourceLinkHeader({ + sourceLink, + showCardOnHover: true, + onClick: (e) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.SOURCE_LINK_CLICK, { + messageId: this.props.messageId, + link: sourceLink.url, + event: e + }); + } + }).render + ] + }).render + ] + })), + this.showMoreButtonBlock.render + ] + }); + } + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-tabbed-card.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-tabbed-card.ts new file mode 100644 index 00000000..a8510e44 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-tabbed-card.ts @@ -0,0 +1,82 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { ChatItemContent, ChatItemType, MynahEventNames } from '../../static'; +import { Tab, ToggleOption } from '../tabs'; +import { ChatItemCard } from './chat-item-card'; +import testIds from '../../helper/test-ids'; +import { emptyChatItemContent } from '../../helper/chat-item'; +import { MynahUIGlobalEvents } from '../../helper/events'; +import { StyleLoader } from '../../helper/style-loader'; + +export interface ChatItemTabbedCardProps { + tabId: string; + messageId: string | undefined; + tabbedCard: NonNullable['tabbedContent']>; + classNames?: string[]; +} + +export class ChatItemTabbedCard { + contentCard: ChatItemCard; + render: ExtendedHTMLElement; + props: ChatItemTabbedCardProps; + + constructor (props: ChatItemTabbedCardProps) { + StyleLoader.getInstance().load('components/chat/_chat-item-card-tabbed-card.scss'); + this.props = props; + const toggleGroup = new Tab({ + options: props.tabbedCard, + direction: 'horizontal', + name: `tabbed-card-toggle-${props.messageId ?? props.tabId}`, + value: this.getTabOfSelectedOrGivenValue().value, + testId: testIds.chatItem.tabbedCard.tabs, + onChange: (value) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.TABBED_CONTENT_SWITCH, { + tabId: this.props.tabId, + messageId: this.props.messageId, + contentTabId: value, + }); + this.contentCard.clearContent(); + this.contentCard.updateCardStack({ + ...emptyChatItemContent, + ...this.getTabOfSelectedOrGivenValue(value).content + }); + } + }); + + const selectedTabContent = (props.tabbedCard.find((tab) => tab.selected) ?? props.tabbedCard[0])?.content; + this.contentCard = new ChatItemCard({ + tabId: props.tabId, + chatItem: { + messageId: props.messageId, + type: ChatItemType.ANSWER, + ...selectedTabContent + } + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-tabbed-card-wrapper', ...props.classNames ?? '' ], + children: [ + { + type: 'div', + classNames: [ 'mynah-tabbed-card-contents' ], + children: [ this.contentCard.render ] + }, + ...(props.tabbedCard.length > 1 + ? [ { + type: 'div', + classNames: [ 'mynah-tabbed-card-tabs' ], + children: [ + toggleGroup.render + ] + } ] + : []) + ] + }); + } + + private readonly getTabOfSelectedOrGivenValue = (value?: string): (ToggleOption & { + content: ChatItemContent; + }) => { + return this.props.tabbedCard.find((tab) => value != null ? tab.value === value : tab.selected) ?? this.props.tabbedCard[0]; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-tree-file.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-file.ts new file mode 100644 index 00000000..facf4d15 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-file.ts @@ -0,0 +1,192 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { MynahUIGlobalEvents, cancelEvent } from '../../helper/events'; +import { FileNodeAction, MynahEventNames, TreeNodeDetails } from '../../static'; +import { Button } from '../button'; +import { Card } from '../card/card'; +import { CardBody } from '../card/card-body'; +import { Icon, MynahIcons, MynahIconsType } from '../icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; +import testIds from '../../helper/test-ids'; +import { parseMarkdown } from '../../helper/marked'; + +export interface ChatItemTreeFileProps { + tabId: string; + messageId: string; + filePath: string; + originalFilePath: string; + fileName: string; + icon?: MynahIcons | MynahIconsType | null; + deleted?: boolean; + details?: TreeNodeDetails; + actions?: FileNodeAction[]; +} + +const PREVIEW_DELAY = 250; +export class ChatItemTreeFile { + render: ExtendedHTMLElement; + private readonly props: ChatItemTreeFileProps; + private fileTooltip: Overlay | null; + private fileTooltipTimeout: ReturnType; + constructor (props: ChatItemTreeFileProps) { + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.fileTree.file, + classNames: [ + 'mynah-chat-item-tree-view-file-item', + 'mynah-button', 'mynah-button-secondary', + this.props.details?.clickable === false ? 'mynah-chat-item-tree-view-not-clickable' : '', + this.props.details?.status != null ? `mynah-chat-item-tree-view-file-item-status-${this.props.details?.status}` : '', + ], + events: { + click: () => { + this.hideTooltip(); + if (this.props.details?.clickable !== false) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FILE_CLICK, { + tabId: this.props.tabId, + messageId: this.props.messageId, + filePath: this.props.originalFilePath, + deleted: this.props.deleted, + fileDetails: this.props.details + }); + } + }, + mouseover: (e) => { + cancelEvent(e); + const textContentSpan: HTMLSpanElement | null = this.render.querySelector('.mynah-chat-item-tree-view-file-item-title-text'); + let tooltipText; + if (textContentSpan != null && textContentSpan.offsetWidth < textContentSpan.scrollWidth) { + tooltipText = parseMarkdown(this.props.fileName, { includeLineBreaks: true }); + } + if (this.props.details?.description != null) { + if (tooltipText != null) { + tooltipText += '\n\n'; + } else { + tooltipText = ''; + } + tooltipText += parseMarkdown(this.props.details?.description ?? '', { includeLineBreaks: true }); + } + if (tooltipText != null) { + this.showTooltip(tooltipText, undefined, OverlayHorizontalDirection.START_TO_RIGHT, textContentSpan); + } + }, + mouseleave: this.hideTooltip + }, + children: [ + ...(this.props.icon != null && this.props.details?.icon === undefined + ? [ { + type: 'span', + classNames: [ 'mynah-chat-single-file-icon' ], + children: [ new Icon({ icon: this.props.icon }).render ] + } ] + : []), + { + type: 'div', + classNames: [ + 'mynah-chat-item-tree-view-file-item-title', + this.props.deleted === true ? 'mynah-chat-item-tree-view-file-item-deleted' : '', + ], + children: [ + ...(this.props.details?.icon !== null ? [ new Icon({ icon: this.props.details?.icon ?? MynahIcons.FILE, status: this.props.details?.iconForegroundStatus }).render ] : []), + { + type: 'span', + classNames: [ 'mynah-chat-item-tree-view-file-item-title-text' ], + children: [ this.props.details?.visibleName ?? this.props.fileName ] + } ] + }, + { + type: 'div', + classNames: [ 'mynah-chat-item-tree-view-file-item-details' ], + children: this.props.details != null + ? [ + ...(this.props.details.changes != null + ? [ { + type: 'span', + classNames: [ 'mynah-chat-item-tree-view-file-item-details-changes' ], + children: [ + ...(this.props.details.changes.added != null ? [ { type: 'span', classNames: [ 'changes-added' ], children: [ `+${this.props.details.changes.added}` ] } ] : []), + ...(this.props.details.changes.deleted != null ? [ { type: 'span', classNames: [ 'changes-deleted' ], children: [ `-${this.props.details.changes.deleted}` ] } ] : []), + ...(this.props.details.changes.total != null ? [ { type: 'span', classNames: [ 'changes-total' ], children: [ `${this.props.details.changes.total}` ] } ] : []), + ] + } ] + : []), + ...(this.props.details?.labelIcon != null ? [ new Icon({ icon: this.props.details?.labelIcon, status: this.props.details?.labelIconForegroundStatus }).render ] : []), + ...(this.props.details.label != null + ? [ { + type: 'span', + classNames: [ 'mynah-chat-item-tree-view-file-item-details-text' ], + children: [ + this.props.details.label + ] + } ] + : []), + ] + : [] + }, + ...(this.props.actions !== undefined + ? [ { + type: 'div', + classNames: [ 'mynah-chat-item-tree-view-file-item-actions' ], + children: this.props.actions.map((action: FileNodeAction) => new Button({ + testId: testIds.chatItem.fileTree.fileAction, + icon: new Icon({ icon: action.icon }).render, + ...(action.label !== undefined ? { label: action.label } : {}), + attributes: { + title: action.description ?? '' + }, + classNames: [ 'mynah-icon-button', action.status ?? '' ], + primary: false, + onClick: (e) => { + cancelEvent(e); + this.hideTooltip(); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FILE_ACTION_CLICK, { + tabId: this.props.tabId, + messageId: this.props.messageId, + filePath: this.props.originalFilePath, + actionName: action.name, + }); + }, + }).render) + } ] + : []), + ] + }); + } + + private readonly showTooltip = (content: string, vDir?: OverlayVerticalDirection, hDir?: OverlayHorizontalDirection, elm?: null | HTMLElement | ExtendedHTMLElement): void => { + if (content.trim() !== '') { + clearTimeout(this.fileTooltipTimeout); + this.fileTooltipTimeout = setTimeout(() => { + clearTimeout(this.fileTooltipTimeout); + this.fileTooltip = new Overlay({ + testId: testIds.chatItem.fileTree.fileTooltipWrapper, + background: true, + closeOnOutsideClick: false, + referenceElement: elm ?? this.render, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: vDir ?? OverlayVerticalDirection.TO_TOP, + horizontalDirection: hDir ?? OverlayHorizontalDirection.CENTER, + children: [ + new Card({ + border: false, + children: [ + new CardBody({ + body: content + }).render + ] + }).render + ], + }); + }, PREVIEW_DELAY); + } + }; + + public readonly hideTooltip = (): void => { + if (this.fileTooltipTimeout != null) { + clearTimeout(this.fileTooltipTimeout); + } + this.fileTooltip?.close(); + this.fileTooltip = null; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view-license.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view-license.ts new file mode 100644 index 00000000..bc0c1a5f --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view-license.ts @@ -0,0 +1,51 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import testIds from '../../helper/test-ids'; +import { ReferenceTrackerInformation } from '../../static'; +import { CardBody } from '../card/card-body'; +import { CollapsibleContent } from '../collapsible-content'; + +export interface ChatItemTreeViewLicenseProps { + referenceSuggestionLabel: string; + references: ReferenceTrackerInformation[]; +} + +export class ChatItemTreeViewLicense { + render: ExtendedHTMLElement; + + constructor (props: ChatItemTreeViewLicenseProps) { + // If no references are found then just return an empty div + if (props.references.length === 0) { + this.render = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'empty' ] + }); + return; + } + + this.render = new CollapsibleContent({ + title: Config.getInstance().config.texts.codeSuggestionWithReferenceTitle, + testId: testIds.chatItem.fileTree.license, + classNames: [ 'mynah-chat-item-tree-view-license' ], + children: [ this.buildDropdownChildren(props.references) ] + }).render; + } + + private readonly buildDropdownChildren = (references: ReferenceTrackerInformation[]): ExtendedHTMLElement => DomBuilder.getInstance().build({ + type: 'ul', + classNames: [ 'mynah-chat-item-tree-view-license-container' ], + children: references.map(ref => ({ + type: 'li', + children: [ + new CardBody({ + body: ref.information + }).render + ] + })), + }); +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view-wrapper.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view-wrapper.ts new file mode 100644 index 00000000..4f0fa78c --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view-wrapper.ts @@ -0,0 +1,104 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import testIds from '../../helper/test-ids'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { fileListToTree } from '../../helper/file-tree'; +import { FileNodeAction, ReferenceTrackerInformation, Status, TreeNodeDetails } from '../../static'; +import { MynahIcons, MynahIconsType } from '../icon'; +import { ChatItemTreeFile } from './chat-item-tree-file'; +import { ChatItemTreeView } from './chat-item-tree-view'; +import { ChatItemTreeViewLicense } from './chat-item-tree-view-license'; + +export interface ChatItemTreeViewWrapperProps { + tabId: string; + messageId: string; + files: string[]; + cardTitle?: string; + classNames?: string[]; + rootTitle?: string; + rootLabel?: string; + rootStatusIcon?: MynahIcons | MynahIconsType; + rootIconForegroundStatus?: Status; + deletedFiles: string[]; + flatList?: boolean; + folderIcon?: MynahIcons | MynahIconsType | null; + actions?: Record; + details?: Record; + hideFileCount?: boolean; + collapsed?: boolean; + referenceSuggestionLabel: string; + references: ReferenceTrackerInformation[]; + onRootCollapsedStateChange: (isCollapsed: boolean) => void; +} + +export class ChatItemTreeViewWrapper { + render: ExtendedHTMLElement; + + constructor (props: ChatItemTreeViewWrapperProps) { + const license = new ChatItemTreeViewLicense({ + referenceSuggestionLabel: props.referenceSuggestionLabel, + references: props.references + }).render; + + const tree = props.files.length === 1 && props.rootTitle == null + ? new ChatItemTreeFile({ + filePath: props.files[0], + fileName: props.files[0], + originalFilePath: props.files[0], + tabId: props.tabId, + messageId: props.messageId, + deleted: props.deletedFiles.includes(props.files[0]), + details: props.details != null ? props.details[props.files[0]] : undefined, + actions: props.actions != null ? props.actions[props.files[0]] : undefined, + icon: MynahIcons.PAPER_CLIP + }).render + : new ChatItemTreeView({ + messageId: props.messageId, + folderIcon: props.folderIcon, + tabId: props.tabId, + node: fileListToTree(props.files, props.deletedFiles, props.actions, props.details, props.rootTitle, props.rootStatusIcon, props.rootIconForegroundStatus, props.rootLabel), + hideFileCount: props.hideFileCount, + collapsed: props.collapsed, + onRootCollapsedStateChange: props.onRootCollapsedStateChange + }).render; + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.fileTree.wrapper, + classNames: [ 'mynah-chat-item-tree-view-wrapper', props.flatList === true ? 'mynah-chat-item-tree-view-flat-list' : '', ...(props.classNames ?? []) ], + children: [ + { + type: 'div', + classNames: [ 'mynah-chat-item-tree-view-wrapper-container' ], + children: [ + ...(props.cardTitle !== '' + ? [ { + type: 'div', + testId: testIds.chatItem.fileTree.title, + classNames: [ 'mynah-chat-item-tree-view-wrapper-title' ], + children: [ + { + type: 'h4', + children: [ `${props.cardTitle ?? Config.getInstance().config.texts.codeSuggestions}` ] + }, + ...(props.hideFileCount !== true + ? [ { + type: 'span', + children: [ `${(props.files?.length ?? 0) + (props.deletedFiles?.length ?? 0)} ${Config.getInstance().config.texts.files}` ] + } ] + : []), + ] + } ] + : []), + license, + tree, + ] + } + ] + }); + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view.ts b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view.ts new file mode 100644 index 00000000..06afb7bd --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-item-tree-view.ts @@ -0,0 +1,148 @@ +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import { TreeNode } from '../../helper/file-tree'; +import testIds from '../../helper/test-ids'; +import { Button } from '../button'; +import { Icon, MynahIcons, MynahIconsType } from '../icon'; +import { ChatItemTreeFile } from './chat-item-tree-file'; + +export interface ChatItemTreeViewProps { + node: TreeNode; + depth?: number; + tabId: string; + messageId: string; + hideFileCount?: boolean; + collapsed?: boolean; + folderIcon?: MynahIcons | MynahIconsType | null; + onRootCollapsedStateChange?: (isCollapsed: boolean) => void; +} + +export class ChatItemTreeView { + private readonly props: ChatItemTreeViewProps; + private readonly node: TreeNode; + private readonly folderIcon: MynahIcons | MynahIconsType | null; + private isOpen: boolean; + private readonly depth: number; + private readonly tabId: string; + private readonly messageId: string; + private readonly hideFileCount: boolean; + render: ExtendedHTMLElement; + + constructor (props: ChatItemTreeViewProps) { + this.props = props; + this.node = props.node; + this.folderIcon = props.folderIcon === null ? null : props.folderIcon ?? MynahIcons.FOLDER; + this.tabId = props.tabId; + this.messageId = props.messageId; + this.hideFileCount = props.hideFileCount ?? false; + this.isOpen = !(props.collapsed ?? false); + this.depth = props.depth ?? 0; + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: this.getClassNames(), + children: [ ...(this.node.type === 'folder' ? this.buildFolderNode() : this.buildFileNode()) ], + }); + } + + getClassNames (): string[] { + return [ + 'mynah-chat-item-tree-view', + this.node.type === 'file' ? 'mynah-chat-tree-view-file' : `mynah-chat-tree-view-folder-${this.isOpen ? 'open' : 'closed'}`, + ]; + } + + updateTree (): void { + this.render.update({ + classNames: this.getClassNames(), + children: [ ...(this.node.type === 'folder' ? this.buildFolderNode() : this.buildFileNode()) ], + }); + } + + buildFolderChildren (): ExtendedHTMLElement[] { + if (this.node.type !== 'folder') return []; + + const folderChildren = this.isOpen + ? this.node.children.map(childNode => + DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-folder-child' ], + children: [ new ChatItemTreeView({ + folderIcon: this.folderIcon, + node: childNode, + depth: this.depth + 1, + tabId: this.tabId, + hideFileCount: this.hideFileCount, + messageId: this.messageId, + }).render ], + }) + ) + : []; + return folderChildren; + } + + buildFolderNode (): ExtendedHTMLElement[] { + if (this.node.type !== 'folder') return []; + const folderItem = new Button({ + testId: testIds.chatItem.fileTree.folder, + icon: new Icon({ icon: this.isOpen ? MynahIcons.DOWN_OPEN : MynahIcons.RIGHT_OPEN }).render, + classNames: [ 'mynah-chat-item-tree-view-button', this.depth === 0 ? 'mynah-chat-item-tree-view-root' : '' ], + label: DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-item-tree-view-button-title' ], + children: [ + ...(this.folderIcon !== null ? [ new Icon({ icon: this.folderIcon }).render ] : []), + { + type: 'span', + children: [ this.node.name ] + }, + ...(this.node.details != null + ? [ + ...(this.node.details.icon != null ? [ new Icon({ icon: this.node.details?.icon, status: this.node.details?.iconForegroundStatus }).render ] : []), + ...(this.node.details.label != null + ? [ { + type: 'span', + classNames: [ 'mynah-chat-item-tree-view-button-weak-title' ], + children: [ this.node.details.label ] + } ] + : []), + ] + : []), + ...(this.hideFileCount + ? [] + : [ { + type: 'span', + classNames: [ 'mynah-chat-item-tree-view-button-weak-title' ], + children: [ `${this.node.children.length} ${Config.getInstance().config.texts.files}` ] + } ]) + ] + }), + primary: false, + onClick: e => { + cancelEvent(e); + this.isOpen = !this.isOpen; + this.props.onRootCollapsedStateChange?.(!this.isOpen); + this.updateTree(); + }, + }).render; + const childrenItems = this.buildFolderChildren(); + return [ folderItem, ...childrenItems ]; + } + + buildFileNode (): ExtendedHTMLElement[] { + if (this.node.type !== 'file') return []; + + const fileItem = new ChatItemTreeFile({ + fileName: this.node.name, + filePath: this.node.filePath, + originalFilePath: this.node.originalFilePath, + tabId: this.tabId, + messageId: this.messageId, + details: this.node.details, + deleted: this.node.deleted, + actions: this.node.actions + }).render; + + return [ fileItem ]; + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-command.ts b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-command.ts new file mode 100644 index 00000000..08bf0401 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-command.ts @@ -0,0 +1,44 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import testIds from '../../helper/test-ids'; + +export interface ChatPromptInputCommandProps { + command: string; + onRemoveClick: () => void; +} +export class ChatPromptInputCommand { + render: ExtendedHTMLElement; + private readonly props: ChatPromptInputCommandProps; + private readonly promptTextInputCommand: ExtendedHTMLElement; + constructor (props: ChatPromptInputCommandProps) { + this.props = props; + this.promptTextInputCommand = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'mynah-chat-prompt-input-command-text' ], + events: { + click: this.props.onRemoveClick + } + }); + this.render = DomBuilder.getInstance().build({ + type: 'span', + testId: testIds.prompt.selectedCommand, + classNames: [ 'mynah-chat-prompt-input-command-wrapper', this.props.command === '' ? 'hidden' : '' ], + children: [ + this.promptTextInputCommand, + ] + }); + } + + setCommand = (command: string): void => { + if (command.trim() === '') { + this.render.addClass('hidden'); + } else { + this.render.removeClass('hidden'); + } + this.promptTextInputCommand.innerText = command; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-info.ts b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-info.ts new file mode 100644 index 00000000..26ac5aec --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-info.ts @@ -0,0 +1,58 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { MynahUITabsStore } from '../../helper/tabs-store'; +import { CardBody } from '../card/card-body'; +import { MynahUIGlobalEvents } from '../../helper/events'; +import { MynahEventNames } from '../../static'; +import testIds from '../../helper/test-ids'; + +export interface ChatPromptInputInfoProps{ + tabId: string; +} +export class ChatPromptInputInfo { + render: ExtendedHTMLElement; + constructor (props: ChatPromptInputInfoProps) { + MynahUITabsStore.getInstance().addListenerToDataStore(props.tabId, 'promptInputInfo', (newInfo: string) => { + if (newInfo != null && newInfo.trim() !== '') { + this.render.update({ + children: [ + new CardBody({ + testId: testIds.prompt.footerInfoBody, + onLinkClick: this.linkClick, + body: newInfo ?? '' + }).render + ] + }); + } else { + this.render.clear(); + } + }); + + const footerInfo = MynahUITabsStore.getInstance().getTabDataStore(props.tabId)?.getValue('promptInputInfo'); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.footerInfo, + classNames: [ 'mynah-chat-prompt-input-info' ], + children: footerInfo != null && footerInfo.trim() !== '' + ? [ + new CardBody({ + testId: testIds.prompt.footerInfoBody, + onLinkClick: this.linkClick, + body: MynahUITabsStore.getInstance().getTabDataStore(props.tabId)?.getValue('promptInputInfo') ?? '' + }).render + ] + : [] + }); + } + + private readonly linkClick = (url: string, e: MouseEvent): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.INFO_LINK_CLICK, { + link: url, + event: e, + }); + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-sticky-card.ts b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-sticky-card.ts new file mode 100644 index 00000000..a3f8f720 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input-sticky-card.ts @@ -0,0 +1,58 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { MynahUITabsStore } from '../../helper/tabs-store'; +import testIds from '../../helper/test-ids'; +import { ChatItemType } from '../../static'; +import { ChatItemCard } from './chat-item-card'; + +export interface ChatPromptInputStickyCardProps{ + tabId: string; +} +export class ChatPromptInputStickyCard { + render: ExtendedHTMLElement; + constructor (props: ChatPromptInputStickyCardProps) { + MynahUITabsStore.getInstance().addListenerToDataStore(props.tabId, 'promptInputStickyCard', (newChatItem) => { + if (newChatItem === null) { + this.render.clear(); + } else { + this.render.update({ + children: [ new ChatItemCard({ + inline: true, + small: true, + chatItem: { + ...newChatItem, + messageId: newChatItem.messageId ?? 'sticky-card', + type: ChatItemType.ANSWER + }, + tabId: props.tabId + }).render ] + }); + } + }); + + const initChatItemForStickyCard = MynahUITabsStore.getInstance().getTabDataStore(props.tabId)?.getValue('promptInputStickyCard'); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.stickyCard, + classNames: [ 'mynah-chat-prompt-input-sticky-card' ], + children: initChatItemForStickyCard !== null + ? [ + new ChatItemCard({ + inline: true, + small: true, + chatItem: { + ...initChatItemForStickyCard, + messageId: initChatItemForStickyCard.messageId ?? 'sticky-card', + type: ChatItemType.ANSWER + }, + tabId: props.tabId + }).render + ] + : [] + }); + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-prompt-input.ts b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input.ts new file mode 100644 index 00000000..28cde645 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-prompt-input.ts @@ -0,0 +1,975 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { ChatItemButton, ChatPrompt, DetailedList, FilterOption, KeyMap, MynahEventNames, PromptAttachmentType, QuickActionCommand, QuickActionCommandGroup, QuickActionCommandsHeader } from '../../static'; +import { TitleDescriptionWithIcon } from '../title-description-with-icon'; +import { MynahUIGlobalEvents, cancelEvent } from '../../helper/events'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; +import { MynahUITabsStore } from '../../helper/tabs-store'; +import escapeHTML from 'escape-html'; +import { ChatPromptInputCommand } from './chat-prompt-input-command'; +import { PromptAttachment } from './prompt-input/prompt-attachment'; +import { PromptInputSendButton } from './prompt-input/prompt-input-send-button'; +import { PromptTextInput } from './prompt-input/prompt-text-input'; +import { Config } from '../../helper/config'; +import testIds from '../../helper/test-ids'; +import { PromptInputProgress } from './prompt-input/prompt-progress'; +import { CardBody } from '../card/card-body'; +import { + convertDetailedListItemToQuickActionCommand, + convertQuickActionCommandGroupsToDetailedListGroups, + filterQuickPickItems, + MARK_CLOSE, + MARK_OPEN +} from '../../helper/quick-pick-data-handler'; +import { DetailedListWrapper } from '../detailed-list/detailed-list'; +import { PromptOptions } from './prompt-input/prompt-options'; +import { PromptInputStopButton } from './prompt-input/prompt-input-stop-button'; +import { PromptTopBar } from './prompt-input/prompt-top-bar/prompt-top-bar'; +import { TopBarButtonOverlayProps } from './prompt-input/prompt-top-bar/top-bar-button'; +import { Button } from '../button'; +import { Icon, MynahIcons } from '../icon'; + +// 96 extra is added as a threshold to allow for attachments +// We ignore this for the textual character limit +export const MAX_USER_INPUT_THRESHOLD = 96; +export const MAX_USER_INPUT = (): number => { + return Config.getInstance().config.maxUserInput - MAX_USER_INPUT_THRESHOLD; +}; + +// The amount of characters in the prompt input necessary for the warning to show +export const INPUT_LENGTH_WARNING_THRESHOLD = (): number => { + return Config.getInstance().config.userInputLengthWarningThreshold; +}; + +export interface ChatPromptInputProps { + tabId: string; + onStopChatResponse?: (tabId: string) => void; +} + +interface UserPrompt { + inputText: string; + codeAttachment: string; +} + +export class ChatPromptInput { + render: ExtendedHTMLElement; + private readonly props: ChatPromptInputProps; + private readonly attachmentWrapper: ExtendedHTMLElement; + private readonly promptTextInput: PromptTextInput; + private readonly contextSelectorButton: Button; + private readonly promptTextInputCommand: ChatPromptInputCommand; + private readonly sendButton: PromptInputSendButton; + private readonly stopButton: PromptInputStopButton; + private readonly progressIndicator: PromptInputProgress; + private readonly promptAttachment: PromptAttachment; + private readonly promptOptions: PromptOptions; + private readonly promptTopBar: PromptTopBar; + private readonly chatPrompt: ExtendedHTMLElement; + private quickPickItemsSelectorContainer: DetailedListWrapper | null; + private promptTextInputLabel: ExtendedHTMLElement; + private remainingCharsOverlay: Overlay | null; + /** + * Preserves cursor position when `@` key is pressed + */ + private quickPickTriggerIndex: number; + /** + * Preserves selection range when `@` key is pressed + */ + private quickPickTriggerRange?: Range; + private quickPickType: 'quick-action' | 'context'; + private quickPickItemGroups: QuickActionCommandGroup[]; + private topBarTitleClicked: boolean = false; + private filteredQuickPickItemGroups: QuickActionCommandGroup[]; + private searchTerm: string = ''; + private quickPick: Overlay; + private quickPickOpen: boolean = false; + private selectedCommand: string = ''; + private readonly userPromptHistory: UserPrompt[] = []; + private userPromptHistoryIndex: number = -1; + private lastUnsentUserPrompt: UserPrompt; + private readonly markerRemovalRegex = new RegExp(`${MARK_OPEN}|${MARK_CLOSE}`, 'g'); + constructor (props: ChatPromptInputProps) { + this.props = props; + this.promptTextInputCommand = new ChatPromptInputCommand({ + command: '', + onRemoveClick: () => { + this.selectedCommand = ''; + this.promptTextInputCommand.setCommand(''); + } + }); + + this.promptTextInput = new PromptTextInput({ + initMaxLength: MAX_USER_INPUT(), + tabId: this.props.tabId, + children: [ this.promptTextInputCommand.render ], + onKeydown: this.handleInputKeydown, + onInput: () => this.updateAvailableCharactersIndicator(), + onFocus: () => { + this.render.addClass('input-has-focus'); + this.handleInputFocus(); + }, + onBlur: () => { + if (this.render.hasClass('awaits-confirmation')) { + this.promptTextInputCommand.setCommand(''); + this.selectedCommand = ''; + this.promptTextInput.updateTextInputPlaceholder(MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputPlaceholder')); + this.promptTextInput.updateTextInputMaxLength(Config.getInstance().config.maxUserInput); + if (Config.getInstance().config.autoFocus) { + this.promptTextInput.focus(); + } + this.render.removeClass('awaits-confirmation'); + } + this.render.removeClass('input-has-focus'); + this.remainingCharsOverlay?.close(); + } + }); + const initText = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputText'); + if (initText != null && initText.trim() !== '') { + this.promptTextInput.updateTextInputValue(initText); + } + this.sendButton = new PromptInputSendButton({ + tabId: this.props.tabId, + onClick: () => { + this.sendPrompt(); + }, + }); + this.stopButton = new PromptInputStopButton({ + tabId: this.props.tabId, + onClick: () => { + if (this.props.onStopChatResponse != null) { + this.props.onStopChatResponse(this.props.tabId); + } + }, + }); + this.progressIndicator = new PromptInputProgress({ + tabId: this.props.tabId, + }); + + this.promptAttachment = new PromptAttachment({ + tabId: this.props.tabId, + }); + + this.promptOptions = new PromptOptions({ + filterOptions: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputOptions'), + buttons: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputButtons'), + onFiltersChange: (formData) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.PROMPT_INPUT_OPTIONS_CHANGE, { + tabId: this.props.tabId, + optionsValues: formData + }); + }, + onButtonClick: (buttonId) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.PROMPT_INPUT_BUTTON_CLICK, { + tabId: this.props.tabId, + buttonId + }); + } + }); + + this.promptTopBar = new PromptTopBar({ + tabId: this.props.tabId, + title: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptTopBarTitle'), + topBarButton: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptTopBarButton'), + contextItems: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptTopBarContextItems'), + onTopBarTitleClick: () => { + this.onContextSelectorButtonClick(true); + }, + onContextItemAdd: (contextItem: QuickActionCommand) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.TOP_BAR_ITEM_ADD, { + tabId: this.props.tabId, + contextItem + }); + }, + onContextItemRemove: (contextItem: QuickActionCommand) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.TOP_BAR_ITEM_REMOVE, { + tabId: this.props.tabId, + contextItem + }); + }, + onTopBarButtonClick: (button: ChatItemButton) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.TOP_BAR_BUTTON_CLICK, { + tabId: this.props.tabId, + button + }); + } + + }); + + this.attachmentWrapper = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.attachmentWrapper, + classNames: [ 'mynah-chat-prompt-attachment-wrapper' ], + children: [ + this.promptAttachment.render + ] + }); + + const noContextCommands = (MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[] ?? []).length === 0; + + this.contextSelectorButton = new Button({ + icon: new Icon({ icon: MynahIcons.AT }).render, + status: 'clear', + disabled: noContextCommands, + classNames: (noContextCommands || !this.promptTopBar.isHidden()) ? [ 'hidden' ] : [], + primary: false, + onClick: () => { + this.onContextSelectorButtonClick(); + }, + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'contextCommands', (contextCommands) => { + if (contextCommands?.length > 0 && this.promptTopBar.isHidden()) { + this.contextSelectorButton.setEnabled(true); + this.contextSelectorButton.render.removeClass('hidden'); + } else { + this.contextSelectorButton.setEnabled(false); + this.contextSelectorButton.render.addClass('hidden'); + } + }); + this.chatPrompt = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-prompt' ], + children: [ + this.progressIndicator.render, + this.chatPrompt, + { + type: 'div', + classNames: [ 'mynah-chat-prompt-input-wrapper' ], + children: [ + this.promptTopBar.render, + this.promptTextInput.render, + { + type: 'div', + classNames: [ 'mynah-chat-prompt-button-wrapper' ], + children: [ + this.promptOptions.render, + this.contextSelectorButton.render, + this.stopButton.render, + this.sendButton.render, + ] + }, + ] + }, + this.attachmentWrapper, + ] + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptInputText', (promptInputText: string) => { + if (this.promptTextInput.getTextInputValue() !== promptInputText) { + this.promptTextInput.clear(); + this.promptTextInput.updateTextInputValue(promptInputText); + setTimeout(() => { + this.promptTextInput.focus(); + }, 750); + } + }); + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptInputOptions', (newFilterOptions: FilterOption[]) => { + this.promptOptions.update(newFilterOptions); + }); + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptInputButtons', (newButtons: ChatItemButton[]) => { + this.promptOptions.update(undefined, newButtons); + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptTopBarContextItems', (newCommands: QuickActionCommand[]) => { + this.promptTopBar.update({ contextItems: newCommands }); + }); + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptTopBarTitle', (newTitle: string) => { + this.promptTopBar.update({ title: newTitle }); + + if (!this.promptTopBar.isHidden()) { + this.contextSelectorButton.setEnabled(false); + this.contextSelectorButton.render.addClass('hidden'); + } + }); + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptTopBarButton', (newButton: ChatItemButton) => { + this.promptTopBar.update({ topBarButton: newButton }); + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptInputLabel', (promptInputLabel: string) => { + const newDetails = this.getPromptInputTextLabel(promptInputLabel); + if (this.promptTextInputLabel != null) { + this.promptTextInputLabel.replaceWith(newDetails); + } else { + this.promptTextInputLabel = newDetails; + } + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'promptInputVisible', (promptInputVisible?: boolean) => { + if (promptInputVisible === false) { + this.render.addClass('hidden'); + } else { + this.render.removeClass('hidden'); + } + }); + + this.promptTextInputLabel = this.getPromptInputTextLabel(MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputLabel')); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.wrapper, + classNames: [ 'mynah-chat-prompt-wrapper', MynahUITabsStore.getInstance().getTabDataStore(props.tabId).getValue('promptInputVisible') === false ? 'hidden' : '' ], + children: [ + this.promptTextInputLabel, + this.chatPrompt + ], + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.ADD_ATTACHMENT, (data: { + textToAdd?: string; + tabId?: string; + type?: PromptAttachmentType; + }) => { + if (this.props.tabId === data.tabId) { + // Code snippet will have a limit of MAX_USER_INPUT - MAX_USER_INPUT_THRESHOLD - current prompt text length + // If exceeding that, we will crop it + const textInputLength = this.promptTextInput.getTextInputValue().trim().length; + const currentSelectedCodeMaxLength = (MAX_USER_INPUT()) - textInputLength; + const croppedAttachmentContent = (data.textToAdd ?? '')?.slice(0, currentSelectedCodeMaxLength); + this.promptAttachment.updateAttachment(croppedAttachmentContent, data.type); + // Also update the limit on prompt text given the selected code + this.promptTextInput.updateTextInputMaxLength(Math.max(MAX_USER_INPUT_THRESHOLD, (MAX_USER_INPUT() - croppedAttachmentContent.length))); + this.updateAvailableCharactersIndicator(); + + // When code is attached, focus to the input with a delay + // Delay is necessary for the render updates + setTimeout(() => { + this.promptTextInput.focus(); + }, 100); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.REMOVE_ATTACHMENT, () => { + this.promptTextInput.updateTextInputMaxLength(MAX_USER_INPUT()); + this.promptAttachment.clear(); + // Update the limit on prompt text given the selected code + this.updateAvailableCharactersIndicator(); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.RESET_TOP_BAR_CLICKED, (data: { tabId: string }) => { + if (this.props.tabId === data.tabId) { + // Reset trigger source to prompt-input after context is inserted + this.topBarTitleClicked = false; + } + }); + } + + private readonly onContextSelectorButtonClick = (topBarTitleClicked?: boolean): void => { + this.searchTerm = ''; + this.quickPickType = 'context'; + this.quickPickItemGroups = (MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? []; + this.quickPickTriggerIndex = this.promptTextInput.getCursorPos(); + this.quickPickTriggerRange = window.getSelection()?.getRangeAt(0); + this.filteredQuickPickItemGroups = [ ...this.quickPickItemGroups ]; + if (topBarTitleClicked !== true) { this.promptTextInput.insertEndSpace(); } + this.openQuickPick(topBarTitleClicked); + }; + + private readonly updateAvailableCharactersIndicator = (): void => { + const characterAmount = MAX_USER_INPUT() - Math.max(0, (this.promptTextInput.promptTextInputMaxLength - this.promptTextInput.getTextInputValue().trim().length)); + const charTextElm = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'mynah-chat-prompt-chars-indicator' ], + innerHTML: `${characterAmount}/${MAX_USER_INPUT()}`, + }); + + // Re(render) if the overlay is not in the DOM, else update + if (this.remainingCharsOverlay == null || this.remainingCharsOverlay.render.parentNode == null) { + this.remainingCharsOverlay = new Overlay({ + testId: testIds.prompt.remainingCharsIndicator, + background: true, + closeOnOutsideClick: false, + referenceElement: this.chatPrompt, + dimOutside: false, + verticalDirection: OverlayVerticalDirection.TO_BOTTOM, + horizontalDirection: OverlayHorizontalDirection.END_TO_LEFT, + children: [ + charTextElm + ], + }); + } else { + this.remainingCharsOverlay.updateContent([ + charTextElm + ]); + } + + // Set the visibility based on whether the threshold is hit + if (characterAmount >= INPUT_LENGTH_WARNING_THRESHOLD()) { + this.remainingCharsOverlay.toggleHidden(false); + } else { + this.remainingCharsOverlay.toggleHidden(true); + } + }; + + private readonly handleInputKeydown = (e: KeyboardEvent): void => { + const navigationalKeys = [ KeyMap.ARROW_UP, KeyMap.ARROW_DOWN ] as string[]; + + if (e.key === KeyMap.ESCAPE && this.render.hasClass('awaits-confirmation')) { + this.promptTextInput.blur(); + } + if (!this.quickPickOpen) { + if (e.key === KeyMap.BACKSPACE || e.key === KeyMap.DELETE) { + if (this.selectedCommand !== '' && this.promptTextInput.getTextInputValue() === '') { + cancelEvent(e); + this.clearTextArea(true); + } + } else if (e.key === KeyMap.ENTER && !e.isComposing) { + const requireModifier = Config.getInstance().config.requireModifierToSendPrompt === true; + const hasModifier = e.shiftKey || e.metaKey || e.ctrlKey; + + if (requireModifier) { + // Modifier required: Shift/Cmd/Ctrl+Enter sends, plain Enter is newline + if (hasModifier) { + cancelEvent(e); + this.sendPrompt(); + } + // Plain Enter: let it through to insert newline + } else { + // Default behavior: Enter sends, Shift+Enter is newline + if (!e.shiftKey && !e.ctrlKey) { + cancelEvent(e); + this.sendPrompt(); + } + } + } else if (e.key === KeyMap.ENTER && e.isComposing && e.shiftKey) { + // IME composition: Shift+Enter sends + cancelEvent(e); + this.sendPrompt(); + } else if ( + (this.selectedCommand === '' && e.key === KeyMap.SLASH && this.promptTextInput.getTextInputValue() === '') || + (e.key === KeyMap.AT && this.promptTextInput.promptTextInputMaxLength > 0) + ) { + const quickPickContextItems = (MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[]) ?? []; + const quickPickCommandItems = (MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('quickActionCommands') as QuickActionCommandGroup[]) ?? []; + this.searchTerm = ''; + this.quickPickType = e.key === KeyMap.AT ? 'context' : 'quick-action'; + this.quickPickItemGroups = this.quickPickType === 'context' ? quickPickContextItems : quickPickCommandItems; + this.quickPickTriggerRange = window.getSelection()?.getRangeAt(0); + this.quickPickTriggerIndex = this.quickPickType === 'context' ? this.promptTextInput.getCursorPos() : 1; + this.filteredQuickPickItemGroups = [ ...this.quickPickItemGroups ]; + this.openQuickPick(); + } else if (navigationalKeys.includes(e.key)) { + const cursorPosition = this.promptTextInput.getCursorPosition(); + + // Only enter history navigation if: + // 1. Going up and there's history to go up to, or we're at the beginning of history navigation + // 2. Going down and we're not already at the bottom of history + const shouldNavigateUp = cursorPosition.isAtTheBeginning && e.key === KeyMap.ARROW_UP && + (this.userPromptHistoryIndex > 0 || this.userPromptHistoryIndex === -1) && + this.userPromptHistory.length > 0; + const shouldNavigateDown = cursorPosition.isAtTheEnd && e.key === KeyMap.ARROW_DOWN && + this.userPromptHistoryIndex !== -1 && + this.userPromptHistoryIndex < this.userPromptHistory.length; + + if (shouldNavigateUp || shouldNavigateDown) { + if (this.userPromptHistoryIndex === -1 || this.userPromptHistoryIndex === this.userPromptHistory.length) { + this.lastUnsentUserPrompt = { + inputText: this.promptTextInput.getTextInputValue(), + codeAttachment: this.promptAttachment?.lastAttachmentContent ?? '', + }; + } + + if (this.userPromptHistoryIndex === -1) { + this.userPromptHistoryIndex = this.userPromptHistory.length; + } + + if (e.key === KeyMap.ARROW_UP) { + // Check if the cursor is on the first line or not + this.userPromptHistoryIndex = Math.max(0, this.userPromptHistoryIndex - 1); + } else if (e.key === KeyMap.ARROW_DOWN) { + // Check if the cursor is on the last line or not + this.userPromptHistoryIndex = Math.min(this.userPromptHistory.length, this.userPromptHistoryIndex + 1); + } + + let codeAttachment = ''; + if (this.userPromptHistoryIndex === this.userPromptHistory.length) { + this.promptTextInput.updateTextInputValue(this.lastUnsentUserPrompt.inputText ?? ''); + codeAttachment = this.lastUnsentUserPrompt.codeAttachment ?? ''; + } else { + this.promptTextInput.updateTextInputValue(this.userPromptHistory[this.userPromptHistoryIndex].inputText); + codeAttachment = this.userPromptHistory[this.userPromptHistoryIndex].codeAttachment ?? ''; + } + codeAttachment = codeAttachment.trim(); + if (codeAttachment.length > 0) { + // the way we mark code in our example mynah client + if (codeAttachment.startsWith('~~~~~~~~~~') && codeAttachment.endsWith('~~~~~~~~~~')) { + codeAttachment = codeAttachment + .replace(/^~~~~~~~~~~/, '') + .replace(/~~~~~~~~~~$/, '') + .trim(); + } else if (codeAttachment.startsWith('```') && codeAttachment.endsWith('```')) { + // the way code is marked in VScode and JetBrains extensions + codeAttachment = codeAttachment + .replace(/^```/, '') + .replace(/```$/, '') + .trim(); + } + this.promptAttachment.updateAttachment(codeAttachment, 'code'); + } else { + this.promptAttachment.clear(); + } + } + } + } else { + const blockedKeys = [ KeyMap.ENTER, KeyMap.ESCAPE, KeyMap.SPACE, KeyMap.TAB, KeyMap.AT, KeyMap.BACK_SLASH, KeyMap.SLASH, KeyMap.ALT ] as string[]; + if (blockedKeys.includes(e.key)) { + // Close quick pick overlay when space is pressed + if (e.key === KeyMap.SPACE) { + this.quickPick?.close(); + return; + } + e.preventDefault(); + if (e.key === KeyMap.ESCAPE) { + if (this.quickPickType === 'quick-action') { + this.clearTextArea(true); + } + this.quickPick?.close(); + } else if (e.key === KeyMap.ENTER || e.key === KeyMap.TAB) { + this.searchTerm = ''; + const targetDetailedListItem = this.quickPickItemsSelectorContainer?.getTargetElement(); + if (targetDetailedListItem != null) { + const commandToSend = convertDetailedListItemToQuickActionCommand(targetDetailedListItem); + if (this.quickPickType === 'context') { + if (commandToSend.command !== '') { + // Add context item to top bar if Alt-Enter is pressed on an item + this.handleContextCommandSelection(commandToSend, e.altKey); + } else { + // Otherwise pass the given text by user + const command = this.promptTextInput.getTextInputValue().substring(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos()); + this.handleContextCommandSelection({ command }); + } + } else { + switch (e.key) { + case KeyMap.TAB: + this.handleQuickActionCommandSelection(commandToSend, 'tab'); + break; + case KeyMap.ENTER: + this.handleQuickActionCommandSelection(commandToSend, 'enter'); + break; + } + } + } + } + } else if (navigationalKeys.includes(e.key)) { + cancelEvent(e); + this.quickPickItemsSelectorContainer?.changeTarget(e.key === KeyMap.ARROW_UP ? 'up' : 'down', true, true); + } else { + if (this.quickPick != null) { + if (this.promptTextInput.getTextInputValue() === '') { + this.quickPick.close(); + } else { + if (e.key === KeyMap.ARROW_LEFT || e.key === KeyMap.ARROW_RIGHT) { + cancelEvent(e); + } else { + this.filteredQuickPickItemGroups = []; + // In case the prompt is an incomplete regex + try { + if (e.key === KeyMap.BACKSPACE) { + const isAllSelected = window.getSelection()?.toString() === this.promptTextInput.getTextInputValue(); + if (this.searchTerm === '' || isAllSelected) { + this.quickPick.close(); + } else { + this.searchTerm = this.searchTerm.slice(0, -1); + } + } else if ((!e.ctrlKey && !e.metaKey) && e.key.length === 1) { + this.searchTerm += e.key.toLowerCase(); + } + this.filteredQuickPickItemGroups = filterQuickPickItems([ ...this.quickPickItemGroups ], this.searchTerm); + } catch (e) {} + if (this.filteredQuickPickItemGroups.length > 0) { + this.quickPick.toggleHidden(false); + this.quickPick.updateContent([ this.getQuickPickItemGroups(this.filteredQuickPickItemGroups) ]); + } else { + // If there's no matching action, hide the command selector overlay + this.quickPick.toggleHidden(true); + } + } + } + } + } + } + }; + + private readonly tabBarTitleOverlayKeyPressHandler = (e: KeyboardEvent): void => { + if (e.key === KeyMap.ARROW_UP || e.key === KeyMap.ARROW_DOWN) { + cancelEvent(e); + this.quickPickItemsSelectorContainer?.changeTarget(e.key === KeyMap.ARROW_UP ? 'up' : 'down', true, true); + } else if (e.key === KeyMap.ENTER) { + const detailedListItem = this.quickPickItemsSelectorContainer?.getTargetElement(); + if (detailedListItem != null) { + const quickPickCommand: QuickActionCommand = convertDetailedListItemToQuickActionCommand(detailedListItem); + this.handleContextCommandSelection(quickPickCommand); + } + } else if (e.key === KeyMap.ESCAPE) { + this.quickPick.close(); + if (Config.getInstance().config.autoFocus) { + this.promptTextInput.focus(); + } + } + }; + + private readonly openQuickPick = (topBarTitleClicked?: boolean): void => { + this.topBarTitleClicked = topBarTitleClicked === true; + + this.quickPickItemsSelectorContainer = null; + + if (this.topBarTitleClicked) { + window.addEventListener('keydown', this.tabBarTitleOverlayKeyPressHandler); + } + + if (this.quickPickItemGroups.length > 0) { + this.quickPick = new Overlay({ + closeOnOutsideClick: true, + referenceElement: this.render.querySelector('.mynah-chat-prompt') as ExtendedHTMLElement, + dimOutside: false, + stretchWidth: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + onClose: () => { + this.quickPickOpen = false; + window.removeEventListener('keydown', this.tabBarTitleOverlayKeyPressHandler); + }, + children: [ + this.getQuickPickItemGroups(this.filteredQuickPickItemGroups) + ], + }); + + this.quickPickOpen = true; + } + }; + + private readonly handleInputFocus = (): void => { + // Show the character limit warning overlay if the threshold is hit + this.updateAvailableCharactersIndicator(); + + const inputValue = this.promptTextInput.getTextInputValue(); + if (inputValue.startsWith('/')) { + const quickPickItems = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('quickActionCommands') as QuickActionCommandGroup[]; + this.quickPickItemGroups = [ ...quickPickItems ]; + this.quickPickTriggerIndex = 1; + const restorePreviousFilteredQuickPickItemGroups: QuickActionCommandGroup[] = []; + this.quickPickItemGroups.forEach((quickPickGroup: QuickActionCommandGroup) => { + const newQuickPickCommandGroup = { ...quickPickGroup }; + try { + const searchTerm = inputValue.substring(this.quickPickTriggerIndex).match(/\S*/gi)?.[0]; + const promptRegex = new RegExp(searchTerm ?? '', 'gi'); + newQuickPickCommandGroup.commands = newQuickPickCommandGroup.commands.filter(command => + command.command.match(promptRegex) + ); + if (newQuickPickCommandGroup.commands.length > 0) { + restorePreviousFilteredQuickPickItemGroups.push(newQuickPickCommandGroup); + } + } catch (e) { + // In case the prompt is an incomplete regex + } + }); + + this.filteredQuickPickItemGroups = [ ...restorePreviousFilteredQuickPickItemGroups ]; + this.openQuickPick(); + } + }; + + private readonly getQuickPickItemGroups = (quickPickGroupList: QuickActionCommandGroup[]): ExtendedHTMLElement => { + const detailedListItemsGroup = convertQuickActionCommandGroupsToDetailedListGroups(quickPickGroupList); + if (this.quickPickItemsSelectorContainer == null) { + const pinContextHint = Config.getInstance().config.texts.pinContextHint; + this.quickPickItemsSelectorContainer = new DetailedListWrapper({ + descriptionTextDirection: 'rtl', + detailedList: { + list: detailedListItemsGroup, + selectable: true, + ...(this.topBarTitleClicked + ? { + filterOptions: [ + { + type: 'textinput', + icon: MynahIcons.SEARCH, + id: 'search', + placeholder: 'Search context', + autoFocus: true, + }, + ], + + } + : !this.promptTopBar.isHidden() && this.quickPickType === 'context' && pinContextHint !== '' + ? { + header: { + description: pinContextHint, + } + } + : {}) + }, + ...(this.topBarTitleClicked + ? { + onFilterValueChange: (filterValues) => { + const searchTerm = filterValues?.search ?? ''; + if (searchTerm.length > 0) { this.filteredQuickPickItemGroups = filterQuickPickItems([ ...this.quickPickItemGroups ], searchTerm, this.topBarTitleClicked); } else { + this.filteredQuickPickItemGroups = [ ...this.quickPickItemGroups ]; + } + const results = convertQuickActionCommandGroupsToDetailedListGroups(this.filteredQuickPickItemGroups); + const emptyResults = results.length === 0 || (results.length === 1 && results[0].children?.length === 0); + this.quickPickItemsSelectorContainer?.update({ list: emptyResults ? [ { groupName: 'No matches found' } ] : results }); + }, + } + : {}), + onGroupActionClick: (action) => { + this.promptTextInput.deleteTextRange(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos()); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.QUICK_COMMAND_GROUP_ACTION_CLICK, { + tabId: this.props.tabId, + actionId: action.id + }); + }, + onItemSelect: (detailedListItem) => { + const quickPickCommand: QuickActionCommand = convertDetailedListItemToQuickActionCommand(detailedListItem); + if (this.quickPickType === 'context') { + this.handleContextCommandSelection(quickPickCommand); + } else { + this.handleQuickActionCommandSelection(quickPickCommand, 'click'); + } + }, + }); + } else { + this.quickPickItemsSelectorContainer.update({ + list: detailedListItemsGroup + }); + } + + const headerInfo: QuickActionCommandsHeader = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('quickActionCommandsHeader'); + let headerComponent = new TitleDescriptionWithIcon({ + ...headerInfo, + classNames: [ 'mynah-chat-prompt-quick-picks-header', `status-${headerInfo.status ?? 'default'}` ] + }).render; + + // const subscriptionId = + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'quickActionCommandsHeader', (newHeader: QuickActionCommandsHeader) => { + const newHeaderComponent = new TitleDescriptionWithIcon({ + ...newHeader, + classNames: [ 'mynah-chat-prompt-quick-picks-header', `status-${newHeader.status ?? 'default'}` ] + }).render; + + headerComponent.replaceWith(newHeaderComponent); + headerComponent = newHeaderComponent; + }); + + // Only show header if it has meaningful content + const hasHeaderContent = headerInfo != null && ( + (headerInfo.title != null && headerInfo.title.trim() !== '') || + (headerInfo.description != null && headerInfo.description.trim() !== '') || + headerInfo.icon != null + ); + + return DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-prompt-quick-picks-overlay-wrapper' ], + children: [ + ...(this.quickPickType === 'quick-action' && hasHeaderContent + ? [ headerComponent ] + : []), + this.quickPickItemsSelectorContainer.render + ] + }); + }; + + private readonly handleQuickActionCommandSelection = ( + dirtyQuickActionCommand: QuickActionCommand, + method: 'enter' | 'tab' | 'space' | 'click'): void => { + const quickActionCommand = { + ...dirtyQuickActionCommand, + command: dirtyQuickActionCommand.command.replace(this.markerRemovalRegex, '') + }; + + this.selectedCommand = quickActionCommand.command; + this.promptTextInput.updateTextInputValue(''); + if (quickActionCommand.placeholder !== undefined) { + this.promptTextInputCommand.setCommand(this.selectedCommand); + this.promptTextInput.updateTextInputPlaceholder(quickActionCommand.placeholder); + if (Config.getInstance().config.autoFocus) { + this.promptTextInput.focus(); + } + } else if (method === 'enter' || method === 'click') { + this.sendPrompt(); + } else { + this.promptTextInputCommand.setCommand(this.selectedCommand); + this.promptTextInput.updateTextInputPlaceholder(Config.getInstance().config.texts.commandConfirmation); + this.promptTextInput.updateTextInputMaxLength(0); + this.render.addClass('awaits-confirmation'); + } + this.quickPick.close(); + }; + + private readonly handleContextCommandSelection = (dirtyContextCommand: QuickActionCommand, topBarHotKey?: boolean): void => { + const contextCommand: QuickActionCommand = { + ...dirtyContextCommand, + command: dirtyContextCommand.command.replace(this.markerRemovalRegex, '') + }; + // Check if the selected command has children + if (contextCommand.children?.[0] != null) { + // If user types '@fi', and then selects a command with children (ex: file command), remove 'fi' from prompt + if (!this.topBarTitleClicked) { this.promptTextInput.deleteTextRange(this.quickPickTriggerIndex + 1, this.promptTextInput.getCursorPos()); } + this.quickPickItemGroups = [ ...contextCommand.children ]; + this.quickPick.updateContent([ + this.getQuickPickItemGroups(contextCommand.children) + ]); + } else { + if (this.quickPickTriggerRange != null) { + // Restore cursor position so element is inserted in correct position + this.promptTextInput.restoreRange(this.quickPickTriggerRange); + } + this.quickPick.close(); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CONTEXT_SELECTED, { + contextItem: contextCommand, + tabId: this.props.tabId, + promptInputCallback: (insert: boolean) => { + if (insert) { + // Add command to top bar if top bar is visible, and either top bar title was clicked or topBarHotKey used + if (!this.promptTopBar.isHidden() && (this.topBarTitleClicked || topBarHotKey === true)) { + this.promptTopBar.addContextPill(contextCommand); + // If user types `@foo` to add context but used topBarHotKey, remove `@foo` from prompt + if (topBarHotKey === true && !this.topBarTitleClicked) { this.promptTextInput.deleteTextRange(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos()); } + } else { + this.promptTextInput.insertContextItem({ + ...contextCommand, + }, this.quickPickTriggerIndex, this.promptTopBar.isHidden()); + } + } else { + this.promptTextInput.deleteTextRange(this.quickPickTriggerIndex, this.promptTextInput.getCursorPos()); + } + } + }); + } + }; + + private readonly sendPrompt = (): void => { + const quickPickItems = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('quickActionCommands') as QuickActionCommandGroup[]; + const currentInputValue = this.promptTextInput.getTextInputValue(); + if (currentInputValue !== '' || this.selectedCommand.trim() !== '') { + let selectedCommand = this.selectedCommand; + + // Catching cases where user could send a prompt with quick action command but the command is not be selected correctly + if (selectedCommand === '') { + for (const quickPickItem of quickPickItems) { + if (selectedCommand !== '') break; + const matchedCommand = quickPickItem.commands.find((item) => item.disabled === false && currentInputValue.startsWith(item.command)); + if (matchedCommand !== undefined) { + selectedCommand = matchedCommand.command; + } + } + } + + const attachmentContent: string | undefined = this.promptAttachment?.lastAttachmentContent; + + // Trim prompt text with command selectedCommand exists + const promptText = this.selectedCommand === '' && selectedCommand !== '' + ? currentInputValue.replace(selectedCommand, '') + (attachmentContent ?? '') + : currentInputValue + (attachmentContent ?? ''); + const context: QuickActionCommand[] = this.promptTextInput.getUsedContext(); + + let escapedPrompt = escapeHTML(promptText); + context?.forEach(cmd => { + if (cmd.command !== '') { + // Escape special regex characters in the command + const escapedCmd = cmd.command.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + // Replace all occurrences of @command with **@command** + escapedPrompt = escapedPrompt.replace( + new RegExp(`@${escapedCmd}`, 'g'), + ` **@${cmd.command}**` + ); + } + }); + + const promptData: {tabId: string; prompt: ChatPrompt} = { + tabId: this.props.tabId, + prompt: { + prompt: promptText, + escapedPrompt, + context, + options: this.promptOptions.getOptionValues() ?? {}, + ...(selectedCommand !== '' ? { command: selectedCommand } : {}), + } + }; + this.clearTextArea(); + + if (currentInputValue !== '') { + this.userPromptHistory.push({ + inputText: currentInputValue, + codeAttachment: attachmentContent ?? '', + }); + } + + this.lastUnsentUserPrompt = { + inputText: '', + codeAttachment: '', + }; + + this.userPromptHistoryIndex = -1; + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CHAT_PROMPT, promptData); + } + }; + + private readonly getPromptInputTextLabel = (promptInputLabel?: string): ExtendedHTMLElement => DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.label, + classNames: [ 'mynah-chat-prompt-input-label' ], + children: promptInputLabel != null && promptInputLabel.trim() !== '' + ? [ + new CardBody({ + body: promptInputLabel + }).render + ] + : [] + }); + + public readonly clearTextArea = (keepAttachment?: boolean): void => { + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).updateStore({ + promptInputText: '', + }); + this.selectedCommand = ''; + this.promptTextInput.clear(); + this.promptTextInput.updateTextInputMaxLength(MAX_USER_INPUT()); + this.promptTextInputCommand.setCommand(''); + if (keepAttachment !== true) { + this.attachmentWrapper.clear(); + this.promptAttachment.clear(); + } + this.updateAvailableCharactersIndicator(); + }; + + public readonly getPromptInputText = (): string => { + return this.promptTextInput.getTextInputValue(); + }; + + public readonly getCursorPosition = (): number => { + return this.promptTextInput.getCursorPos(); + }; + + public readonly addAttachment = (attachmentContent: string, type?: PromptAttachmentType): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.ADD_ATTACHMENT, { + textToAdd: attachmentContent, + tabId: this.props.tabId, + type + }); + }; + + public readonly openTopBarButtonItemOverlay = (data: TopBarButtonOverlayProps): void => { + this.promptTopBar.topBarButton.showOverlay(data); + }; + + public readonly updateTopBarButtonItemOverlay = (data: DetailedList): void => { + this.promptTopBar.topBarButton.onTopBarButtonOverlayChanged(data); + }; + + public readonly closeTopBarButtonItemOverlay = (): void => { + this.promptTopBar.topBarButton.closeOverlay(); + }; + + public readonly destroy = (): void => { + this.promptTextInput.destroy(); + }; + + public readonly getCurrentTriggerSource = (): 'top-bar' | 'prompt-input' => { + return this.topBarTitleClicked ? 'top-bar' : 'prompt-input'; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/chat-wrapper.ts b/vendor/mynah-ui/src/components/chat-item/chat-wrapper.ts new file mode 100644 index 00000000..689ae9c7 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/chat-wrapper.ts @@ -0,0 +1,544 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement, DomBuilderObject } from '../../helper/dom'; +import { generateUID } from '../../helper/guid'; +import { MynahUITabsStore } from '../../helper/tabs-store'; +import { + CardRenderDetails, + ChatItem, + ChatItemType, + DetailedList, + PromptAttachmentType, + TabHeaderDetails, + MynahEventNames, + QuickActionCommandGroup, + QuickActionCommand +} from '../../static'; +import { ChatItemCard } from './chat-item-card'; +import { ChatPromptInput } from './chat-prompt-input'; +import { ChatPromptInputInfo } from './chat-prompt-input-info'; +import { ChatPromptInputStickyCard } from './chat-prompt-input-sticky-card'; +import testIds from '../../helper/test-ids'; +import { TitleDescriptionWithIcon } from '../title-description-with-icon'; +import { GradientBackground } from '../background'; +import { MoreContentIndicator } from '../more-content-indicator'; +import { StyleLoader } from '../../helper/style-loader'; +import { Icon } from '../icon'; +import { cancelEvent, MynahUIGlobalEvents } from '../../helper/events'; +import { TopBarButtonOverlayProps } from './prompt-input/prompt-top-bar/top-bar-button'; + +export const CONTAINER_GAP = 12; +export interface ChatWrapperProps { + onStopChatResponse?: (tabId: string) => void; + tabId: string; +} +export class ChatWrapper { + private readonly props: ChatWrapperProps; + private readonly chatItemsContainer: ExtendedHTMLElement; + private readonly promptInputElement: ExtendedHTMLElement; + private readonly promptInput: ChatPromptInput; + private readonly footerSpacer: ExtendedHTMLElement; + private readonly headerSpacer: ExtendedHTMLElement; + private readonly promptInfo: ExtendedHTMLElement; + private readonly promptStickyCard: ExtendedHTMLElement; + private canObserveIntersection: boolean = false; + private observer: IntersectionObserver | null; + private activeConversationGroup: ExtendedHTMLElement; + private tabHeaderDetails: ExtendedHTMLElement; + private tabModeSwitchTimeout: ReturnType | null; + private lastStreamingChatItemCard: ChatItemCard | null; + private lastStreamingChatItemMessageId: string | null; + private allRenderedChatItems: Record = {}; + render: ExtendedHTMLElement; + private readonly dragOverlayContent: HTMLElement; + private readonly dragBlurOverlay: HTMLElement; + private dragOverlayVisibility: boolean = true; + private imageContextFeatureEnabled: boolean = false; + + constructor (props: ChatWrapperProps) { + StyleLoader.getInstance().load('components/chat/_chat-wrapper.scss'); + + this.props = props; + this.footerSpacer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-wrapper-footer-spacer' ] + }); + this.headerSpacer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-wrapper-header-spacer' ] + }); + + /* + The IDE controls image-related functionality through the imageContextEnabled feature flag. + When this flag is set to true, the language server adds the Image option to the available context types. + + Users can add images to the context in Mynah UI through three methods: + + 1) Using the context command menu (image option in the context menu added by the language server) + 2) Typing the @image: command + 3) Dragging and dropping images + + To maintain consistency, we've implemented a centralized feature flag that controls the visibility + of all three image-adding methods. This ensures that image functionality is either entirely available + or unavailable across for an IDE. + */ + + const contextCommandsRaw = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands'); + const contextCommands = Array.isArray(contextCommandsRaw) ? contextCommandsRaw : []; + this.imageContextFeatureEnabled = contextCommands.some(group => + group.commands.some((cmd: QuickActionCommand) => cmd.command.toLowerCase() === 'image') + ); + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'chatItems', (chatItems: ChatItem[]) => { + const chatItemToInsert: ChatItem = chatItems[chatItems.length - 1]; + if (Object.keys(this.allRenderedChatItems).length === chatItems.length) { + const lastItem = this.chatItemsContainer.children.item(Array.from(this.chatItemsContainer.children).length - 1); + if (lastItem != null && chatItemToInsert != null) { + const newChatItemCard = new ChatItemCard({ tabId: this.props.tabId, chatItem: chatItemToInsert }); + if (chatItemToInsert.messageId !== undefined) { + this.allRenderedChatItems[chatItemToInsert.messageId] = newChatItemCard; + } + lastItem.replaceWith(newChatItemCard.render); + } + } else if (chatItems.length > 0) { + if (Object.keys(this.allRenderedChatItems).length === 0) { + chatItems.forEach(chatItem => { + this.insertChatItem(chatItem); + }); + } else { + this.insertChatItem(chatItemToInsert); + } + } else { + this.chatItemsContainer.clear(true); + this.chatItemsContainer.insertChild('beforeend', this.getNewConversationGroupElement()); + this.allRenderedChatItems = {}; + } + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'loadingChat', (loadingChat: boolean) => { + if (loadingChat) { + this.render.addClass('loading'); + } else { + this.render.removeClass('loading'); + } + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'tabHeaderDetails', (tabHeaderDetails: TabHeaderDetails) => { + this.render.addClass('tab-mode-switch-animation'); + if (this.tabModeSwitchTimeout != null) { + clearTimeout(this.tabModeSwitchTimeout); + } + this.tabModeSwitchTimeout = setTimeout(() => { + this.render.removeClass('tab-mode-switch-animation'); + this.tabModeSwitchTimeout = null; + if (tabHeaderDetails == null) { + this.tabHeaderDetails.clear(); + } + }, 750); + + if (tabHeaderDetails != null) { + // Update view + const newDetails = new TitleDescriptionWithIcon({ + testId: testIds.chat.header, + classNames: [ 'mynah-ui-tab-header-details' ], + ...tabHeaderDetails + }).render; + if (this.tabHeaderDetails != null) { + this.tabHeaderDetails.replaceWith(newDetails); + } else { + this.tabHeaderDetails = newDetails; + } + + this.render.addClass('show-tab-header-details'); + } else { + this.render.removeClass('show-tab-header-details'); + } + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'compactMode', (compactMode: boolean) => { + this.render.addClass('tab-mode-switch-animation'); + if (this.tabModeSwitchTimeout != null) { + clearTimeout(this.tabModeSwitchTimeout); + } + this.tabModeSwitchTimeout = setTimeout(() => { + this.render.removeClass('tab-mode-switch-animation'); + this.tabModeSwitchTimeout = null; + }, 750); + + if (compactMode) { + this.render.addClass('compact-mode'); + } else { + this.render.removeClass('compact-mode'); + } + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(props.tabId, 'contextCommands', (contextCommands: QuickActionCommandGroup[]) => { + // Feature flag for image context command + this.imageContextFeatureEnabled = contextCommands?.some(group => + group.commands.some((cmd: QuickActionCommand) => cmd.command.toLowerCase() === 'image') + ); + }); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'tabBackground', (tabBackground: boolean) => { + if (tabBackground) { + this.render.addClass('with-background'); + } else { + this.render.removeClass('with-background'); + } + }); + + this.chatItemsContainer = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chat.chatItemsContainer, + classNames: [ 'mynah-chat-items-container' ], + persistent: true, + children: [ + this.getNewConversationGroupElement() + ], + }); + + this.tabHeaderDetails = new TitleDescriptionWithIcon({ + classNames: [ 'mynah-ui-tab-header-details' ], + ...MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('tabHeaderDetails') + }).render; + + this.promptInfo = new ChatPromptInputInfo({ tabId: this.props.tabId }).render; + this.promptStickyCard = new ChatPromptInputStickyCard({ tabId: this.props.tabId }).render; + if (Config.getInstance().config.showPromptField) { + this.promptInput = new ChatPromptInput({ tabId: this.props.tabId, onStopChatResponse: this.props?.onStopChatResponse }); + this.promptInputElement = this.promptInput.render; + } + + // Always-present drag overlays (hidden by default, shown by style) + const dragOverlayIcon = Config.getInstance().config.dragOverlayIcon; + const dragOverlayText = Config.getInstance().config.texts.dragOverlayText; + const dragOverlayChildren: Array = []; + if (dragOverlayIcon !== undefined) { + dragOverlayChildren.push(new Icon({ icon: dragOverlayIcon }).render); + } + if (dragOverlayText !== undefined) { + dragOverlayChildren.push({ type: 'span', children: [ dragOverlayText ] } satisfies DomBuilderObject); + } + this.dragOverlayContent = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-drag-overlay-content' ], + children: dragOverlayChildren + }); + this.dragBlurOverlay = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-drag-blur-overlay' ] + }); + // Set display:none initially + this.setDragOverlayVisible(false); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chat.wrapper, + classNames: [ 'mynah-chat-wrapper', + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('tabHeaderDetails') != null ? 'show-tab-header-details' : '', + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('compactMode') === true ? 'compact-mode' : '', + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('tabBackground') === true ? 'with-background' : '' + ], + attributes: { + 'mynah-tab-id': this.props.tabId, + }, + persistent: true, + events: { + dragenter: (e: DragEvent) => { + if (!this.imageContextFeatureEnabled) return; + cancelEvent(e); + if ((e.dataTransfer?.types.includes('Files')) === true) { + this.setDragOverlayVisible(true); + } + }, + dragover: (e: DragEvent) => { + if (!this.imageContextFeatureEnabled) return; + cancelEvent(e); + }, + dragleave: (e: DragEvent) => { + if (!this.imageContextFeatureEnabled) return; + cancelEvent(e); + if (e.relatedTarget === null || !this.render.contains(e.relatedTarget as Node)) { + this.setDragOverlayVisible(false); + } + }, + drop: (e: DragEvent) => { + if (!this.imageContextFeatureEnabled) return; + cancelEvent(e); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.RESET_TOP_BAR_CLICKED, { tabId: this.props.tabId }); + const files = Array.from(e.dataTransfer?.files ?? []); + files.filter(file => file.type.startsWith('image/')); + // Get the current cursor position of prompt input + const cursorPosition = this.getPromptInputCursorPosition(); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FILES_DROPPED, { + tabId: this.props.tabId, + insertPosition: cursorPosition, + files + }); + this.setDragOverlayVisible(false); + }, + dragend: (e: DragEvent) => { + if (!this.imageContextFeatureEnabled) return; + this.setDragOverlayVisible(false); + } + }, + children: [ + { + type: 'style', + children: [ ` + .mynah-nav-tabs-wrapper[selected-tab="${this.props.tabId}"] ~ .mynah-ui-tab-contents-wrapper > .mynah-chat-wrapper[mynah-tab-id="${this.props.tabId}"]{ + visibility: visible; + position: relative; + left: initial; + opacity: 1; + } + .mynah-nav-tabs-wrapper[selected-tab="${this.props.tabId}"] ~ .mynah-ui-tab-contents-wrapper > .mynah-chat-wrapper:not([mynah-tab-id="${this.props.tabId}"]) * { + pointer-events: none !important; + } + ` ], + }, + (new GradientBackground()).render, + this.headerSpacer, + this.tabHeaderDetails, + this.chatItemsContainer, + new MoreContentIndicator({ + border: false, + onClick: () => { + this.chatItemsContainer.scrollTop = this.chatItemsContainer.scrollHeight; + } + }).render, + this.promptStickyCard, + this.promptInputElement, + this.footerSpacer, + this.promptInfo, + this.dragBlurOverlay, + this.dragOverlayContent + ] + }); + + const initChatItems = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('chatItems'); + if (initChatItems.length > 0) { + initChatItems.forEach((chatItem: ChatItem) => this.insertChatItem(chatItem)); + } + } + + private readonly getNewConversationGroupElement = (): ExtendedHTMLElement => { + this.activeConversationGroup?.querySelector('.intersection-observer')?.remove(); + this.activeConversationGroup = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chat.conversationContainer, + classNames: [ 'mynah-chat-items-conversation-container' ], + children: [ + { + type: 'span', + classNames: [ 'intersection-observer' ] + } + ], + }); + if (this.observer == null && IntersectionObserver != null) { + this.observer = new IntersectionObserver((entries) => { + if (this.canObserveIntersection) { + if (!entries[0].isIntersecting) { + this.render?.addClass('more-content'); + } else if (this.canObserveIntersection) { + this.canObserveIntersection = false; + this.render?.removeClass('more-content'); + const previousObserverElement = this.activeConversationGroup.querySelector('.intersection-observer'); + if (previousObserverElement != null) { + this.observer?.unobserve(previousObserverElement); + } + } + } + }); + } else { + const previousObserverElement = this.activeConversationGroup.querySelector('.intersection-observer'); + if (previousObserverElement != null) { + this.observer?.unobserve(previousObserverElement); + } + } + setTimeout(() => { + this.canObserveIntersection = true; + }, 500); + this.canObserveIntersection = false; + this.render?.removeClass('more-content'); + this.observer?.observe(this.activeConversationGroup.querySelector('.intersection-observer') as HTMLSpanElement); + return this.activeConversationGroup; + }; + + private readonly removeEmptyCardsAndFollowups = (): void => { + Object.keys(this.allRenderedChatItems).forEach(messageId => { + if (this.allRenderedChatItems[messageId].cleanFollowupsAndRemoveIfEmpty()) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.allRenderedChatItems[messageId]; + } + }); + }; + + private readonly insertChatItem = (chatItem: ChatItem): void => { + this.removeEmptyCardsAndFollowups(); + const currentMessageId: string = (chatItem.messageId != null && chatItem.messageId !== '') ? chatItem.messageId : `TEMP_${generateUID()}`; + const chatItemCard = new ChatItemCard({ + tabId: this.props.tabId, + chatItem: { + ...chatItem, + messageId: currentMessageId + } + }); + + // When a new card appears, we're cleaning the last streaming card vars, since it is not the last anymore + if (this.lastStreamingChatItemMessageId != null) { + this.endStreamWithMessageId(this.lastStreamingChatItemMessageId, {}); + } + + if (chatItem.type === ChatItemType.ANSWER_STREAM) { + // Update the lastStreaming variables with the new one + this.lastStreamingChatItemMessageId = currentMessageId; + this.lastStreamingChatItemCard = chatItemCard; + } + + if (chatItem.type === ChatItemType.PROMPT) { + this.chatItemsContainer.insertChild('beforeend', this.getNewConversationGroupElement()); + } + + // Add to render + this.activeConversationGroup.insertChild('beforeend', chatItemCard.render); + + // Add to all rendered chat items map + this.allRenderedChatItems[currentMessageId] = chatItemCard; + + if (chatItem.type === ChatItemType.PROMPT || chatItem.type === ChatItemType.SYSTEM_PROMPT) { + // Make sure we align to top when there is a new prompt. + // Only if it is a PROMPT! + // Check css application + this.chatItemsContainer.addClass('set-scroll'); + } + + setTimeout(() => { + // remove css class which allows us to snap automatically + this.chatItemsContainer.removeClass('set-scroll'); + }, 100); + }; + + private readonly checkLastAnswerStreamChange = (updateWith: Partial): void => { + // If the new type is not a stream anymore + // Clear lastStremingMessage variables. + if (updateWith.type !== undefined && + updateWith.type !== null && + updateWith.type !== ChatItemType.ANSWER_STREAM && + updateWith.type !== ChatItemType.ANSWER_PART) { + this.lastStreamingChatItemCard = null; + this.lastStreamingChatItemMessageId = null; + } + }; + + public updateLastChatAnswer = (updateWith: Partial): void => { + if (this.lastStreamingChatItemCard != null) { + this.lastStreamingChatItemCard.updateCardStack(updateWith); + if (updateWith.messageId != null && updateWith.messageId !== '') { + if (this.lastStreamingChatItemMessageId != null && this.lastStreamingChatItemMessageId !== updateWith.messageId) { + const renderChatItemInMap = this.allRenderedChatItems[this.lastStreamingChatItemMessageId]; + if (renderChatItemInMap != null) { + this.allRenderedChatItems[updateWith.messageId] = renderChatItemInMap; + if (this.lastStreamingChatItemMessageId != null) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.allRenderedChatItems[this.lastStreamingChatItemMessageId]; + } + } + } + this.lastStreamingChatItemMessageId = updateWith.messageId; + } + + this.checkLastAnswerStreamChange(updateWith); + } + }; + + public getLastStreamingMessageId = (): string | null => { + return this.lastStreamingChatItemMessageId; + }; + + public getChatItem = (messageId: string): { + chatItem: ChatItem; + render: ExtendedHTMLElement | HTMLElement; + renderDetails: CardRenderDetails; + } | undefined => { + if (this.allRenderedChatItems[messageId]?.render !== undefined) { + return { + chatItem: this.allRenderedChatItems[messageId].props.chatItem, + render: this.allRenderedChatItems[messageId].render, + renderDetails: this.allRenderedChatItems[messageId].getRenderDetails() + }; + } + }; + + public endStreamWithMessageId = (messageId: string, updateWith: Partial): void => { + if (this.allRenderedChatItems[messageId]?.render !== undefined) { + this.allRenderedChatItems[messageId].render.addClass('stream-ended'); + + // End typewriter animation stream to flush remaining updates instantly + this.allRenderedChatItems[messageId].endStream(); + + this.updateChatAnswerWithMessageId(messageId, updateWith); + + // If the last streaming chat answer is the same with the messageId + if (this.lastStreamingChatItemMessageId === messageId) { + this.lastStreamingChatItemCard = null; + this.lastStreamingChatItemMessageId = null; + } + } + }; + + public updateChatAnswerWithMessageId = (messageId: string, updateWith: Partial): void => { + if (this.allRenderedChatItems[messageId]?.render !== undefined) { + this.allRenderedChatItems[messageId].updateCardStack(updateWith); + + // If the last streaming chat answer is the same with the messageId + if (this.lastStreamingChatItemMessageId === messageId) { + this.checkLastAnswerStreamChange(updateWith); + } + } + }; + + public addAttachmentToPrompt = (textToAdd: string, type?: PromptAttachmentType): void => { + this.promptInput.addAttachment(textToAdd, type); + }; + + public openTopBarButtonItemOverlay = (data: TopBarButtonOverlayProps): void => { + this.promptInput.openTopBarButtonItemOverlay(data); + }; + + public updateTopBarButtonItemOverlay = (data: DetailedList): void => { + this.promptInput.updateTopBarButtonItemOverlay(data); + }; + + public closeTopBarButtonItemOverlay = (): void => { + this.promptInput.closeTopBarButtonItemOverlay(); + }; + + public getPromptInputText = (): string => { + return this.promptInput.getPromptInputText(); + }; + + public getPromptInputCursorPosition = (): number => { + return this.promptInput.getCursorPosition(); + }; + + public destroy = (): void => { + if (this.observer != null) { + this.observer.disconnect(); + this.observer = null; + } + }; + + public getCurrentTriggerSource (): 'top-bar' | 'prompt-input' { + return this.promptInput?.getCurrentTriggerSource?.() ?? 'prompt-input'; + } + + public setDragOverlayVisible (visible: boolean): void { + if (this.dragOverlayVisibility === visible) return; + this.dragOverlayVisibility = visible; + this.dragOverlayContent.style.display = visible ? 'flex' : 'none'; + this.dragBlurOverlay.style.display = visible ? 'block' : 'none'; + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-attachment.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-attachment.ts new file mode 100644 index 00000000..5600c8d5 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-attachment.ts @@ -0,0 +1,69 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../../helper/dom'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { PromptAttachmentType } from '../../../static'; +import { PromptTextAttachment } from './prompt-text-attachment'; + +export interface PromptAttachmentProps { + tabId: string; +} + +export class PromptAttachment { + render: ExtendedHTMLElement; + lastAttachmentContent: string = ''; + private readonly props: PromptAttachmentProps; + private attachmentItem: PromptTextAttachment | undefined; + constructor (props: PromptAttachmentProps) { + this.props = props; + + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'outer-container' ], + persistent: true, + }); + } + + public readonly updateAttachment = (attachmentContent: string | undefined, type?: PromptAttachmentType): void => { + if (this.attachmentItem !== undefined) { + this.attachmentItem.clear(); + } + this.render.clear(); + this.lastAttachmentContent = attachmentContent != null + ? (type === 'code' + ? ` +~~~~~~~~~~ +${attachmentContent} +~~~~~~~~~~` + : ` +${attachmentContent} +`) + : ''; + if (attachmentContent !== undefined && attachmentContent !== '') { + this.attachmentItem = new PromptTextAttachment({ + tabId: this.props.tabId, + content: attachmentContent, + type: type ?? 'markdown' + }); + this.render.insertChild('afterbegin', this.attachmentItem.render); + const isCodeOverflowVertically = + (this.render.getBoundingClientRect()?.height ?? 0) < (this.render.getElementsByTagName('code')?.[0]?.getBoundingClientRect()?.height ?? 0); + if (isCodeOverflowVertically) { + this.render.children[0].classList.add('vertical-overflow'); + } + } + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId)?.updateStore({ + selectedCodeSnippet: attachmentContent, + }); + }; + + public readonly clear = (): void => { + this.lastAttachmentContent = ''; + if (this.attachmentItem !== undefined) { + this.attachmentItem.clear(); + } + this.attachmentItem = undefined; + this.render.clear(); + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId)?.updateStore({ + selectedCodeSnippet: undefined, + }); + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-input-send-button.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-input-send-button.ts new file mode 100644 index 00000000..0a41641f --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-input-send-button.ts @@ -0,0 +1,44 @@ +import { ExtendedHTMLElement } from '../../../helper/dom'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import testIds from '../../../helper/test-ids'; +import { Button } from '../../button'; +import { Icon, MynahIcons } from '../../icon'; + +export interface SendButtonProps { + tabId: string; + onClick: () => void; +} + +export class PromptInputSendButton { + render: ExtendedHTMLElement; + private readonly props: SendButtonProps; + constructor (props: SendButtonProps) { + this.props = props; + + const initialDisabledState = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputDisabledState') as boolean; + + this.render = new Button({ + testId: testIds.prompt.send, + classNames: [ 'mynah-chat-prompt-button' ], + attributes: { + ...(initialDisabledState ? { disabled: 'disabled' } : {}), + tabindex: '0' + }, + icon: new Icon({ icon: MynahIcons.ENTER }).render, + primary: false, + border: false, + status: 'clear', + onClick: () => { + this.props.onClick(); + }, + }).render; + + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).subscribe('promptInputDisabledState', (isDisabled: boolean) => { + if (isDisabled) { + this.render.setAttribute('disabled', 'disabled'); + } else { + this.render.removeAttribute('disabled'); + } + }); + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-input-stop-button.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-input-stop-button.ts new file mode 100644 index 00000000..218b7202 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-input-stop-button.ts @@ -0,0 +1,57 @@ +import { Config } from '../../../helper/config'; +import { ExtendedHTMLElement } from '../../../helper/dom'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import testIds from '../../../helper/test-ids'; +import { Button } from '../../button'; +import { Icon, MynahIcons } from '../../icon'; +import { OverlayHorizontalDirection } from '../../overlay'; + +export interface PromptInputStopButtonPrompts { + tabId: string; + onClick: () => void; +} + +export class PromptInputStopButton { + render: ExtendedHTMLElement; + private readonly props: PromptInputStopButtonPrompts; + constructor (props: PromptInputStopButtonPrompts) { + this.props = props; + const tabStore = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId); + + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'cancelButtonWhenLoading', (isVisible) => { + this.checkVisibilityState(isVisible, tabStore.getValue('loadingChat')); + }); + MynahUITabsStore.getInstance().addListenerToDataStore(this.props.tabId, 'loadingChat', (isLoading) => { + this.checkVisibilityState(tabStore.getValue('cancelButtonWhenLoading'), isLoading); + }); + + this.render = new Button({ + testId: testIds.prompt.send, + classNames: [ + 'mynah-chat-prompt-stop-button', 'hidden' + ], + attributes: { + tabindex: '0' + }, + label: Config.getInstance().config.texts.stopGenerating, + icon: new Icon({ icon: MynahIcons.STOP }).render, + primary: false, + border: false, + tooltip: Config.getInstance().config.texts.stopGeneratingTooltip ?? Config.getInstance().config.texts.stopGenerating, + tooltipHorizontalDirection: OverlayHorizontalDirection.END_TO_LEFT, + status: 'clear', + onClick: () => { + this.props.onClick(); + }, + }).render; + this.checkVisibilityState(tabStore.getValue('cancelButtonWhenLoading'), tabStore.getValue('loadingChat')); + } + + private readonly checkVisibilityState = (isVisible: boolean, loadingState: boolean): void => { + if (isVisible && loadingState) { + this.render.removeClass('hidden'); + } else { + this.render.addClass('hidden'); + } + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-options.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-options.ts new file mode 100644 index 00000000..3547448c --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-options.ts @@ -0,0 +1,83 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../../helper/dom'; +import { ChatItemButton, FilterOption } from '../../../static'; +import testIds from '../../../helper/test-ids'; +import { ChatItemFormItemsWrapper } from '../chat-item-form-items'; +import { Button } from '../../button'; +import { Icon } from '../../icon'; +import { OverlayHorizontalDirection, OverlayVerticalDirection } from '../../overlay'; + +export interface PromptOptionsProps { + classNames?: string[]; + filterOptions: FilterOption[]; + buttons: ChatItemButton[]; + onFiltersChange?: (filterFormData: Record, isValid: boolean) => void; + onButtonClick?: (buttonId: string) => void; +} + +export class PromptOptions { + render: ExtendedHTMLElement; + private readonly props: PromptOptionsProps; + private formItemsWrapper: ChatItemFormItemsWrapper; + constructor (props: PromptOptionsProps) { + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.options, + classNames: [ 'mynah-prompt-input-options', ...(this.props.classNames ?? []) ], + children: this.getFilterOptionsWrapper() + }); + } + + private readonly getFilterOptionsWrapper = (): Array => { + let result: Array = [ '' ]; + if (this.props.filterOptions?.length > 0) { + this.formItemsWrapper = new ChatItemFormItemsWrapper({ + tabId: '', + chatItem: { + formItems: this.props.filterOptions + }, + onFormChange: this.props.onFiltersChange + }); + result = [ + this.formItemsWrapper.render + ]; + } + if (this.props.buttons?.length > 0) { + this.props.buttons.forEach((button: ChatItemButton) => { + result.push(new Button({ + onClick: () => { + this.props.onButtonClick?.(button.id); + }, + border: false, + primary: false, + status: button.status, + label: button.text, + disabled: button.disabled, + tooltip: button.description, + fillState: 'always', + tooltipHorizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + tooltipVerticalDirection: OverlayVerticalDirection.TO_TOP, + ...(button.icon != null ? { icon: new Icon({ icon: button.icon }).render } : {}), + }).render); + }); + } + + return result; // [ '' ]; + }; + + public readonly update = (filterOptions?: FilterOption[], buttons?: ChatItemButton[]): void => { + if (filterOptions != null) { + this.props.filterOptions = filterOptions; + } + if (buttons != null) { + this.props.buttons = buttons; + } + this.render.update({ + children: this.getFilterOptionsWrapper() + }); + }; + + public readonly getOptionValues = (): Record>> => { + return this.formItemsWrapper?.getAllValues() ?? {}; + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-progress.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-progress.ts new file mode 100644 index 00000000..1e0730c4 --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-progress.ts @@ -0,0 +1,46 @@ +import { ExtendedHTMLElement } from '../../../helper/dom'; +import { cancelEvent, MynahUIGlobalEvents } from '../../../helper/events'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import testIds from '../../../helper/test-ids'; +import { MynahEventNames, ProgressField } from '../../../static'; +import { ProgressIndicator } from '../../progress'; + +export interface PromptInputProgressProps { + tabId: string; +} + +export class PromptInputProgress { + render: ExtendedHTMLElement; + private readonly progressIndicator: ProgressIndicator; + private progressData: ProgressField; + private readonly props: PromptInputProgressProps; + constructor (props: PromptInputProgressProps) { + this.props = props; + this.progressData = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputProgress') ?? {}; + this.progressIndicator = new ProgressIndicator({ + testId: testIds.prompt.progress, + classNames: [ 'mynah-prompt-input-progress-field' ], + ...this.progressData, + onActionClick: (action, e) => { + if (e != null) { + cancelEvent(e); + } + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.PROMPT_PROGRESS_ACTION_CLICK, { + tabId: this.props.tabId, + actionId: action.id, + actionText: action.text + }); + } + }); + this.render = this.progressIndicator.render; + + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).subscribe('promptInputProgress', (progressData) => { + this.progressData = progressData; + if (this.progressData === null) { + this.progressIndicator.update(null); + } else { + this.progressIndicator.update(this.progressData); + } + }); + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-text-attachment.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-text-attachment.ts new file mode 100644 index 00000000..39e020cb --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-text-attachment.ts @@ -0,0 +1,103 @@ +import { ExtendedHTMLElement } from '../../../helper/dom'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../../overlay'; +import { Icon, MynahIcons } from '../../icon'; +import { Button } from '../../button'; +import { MynahUIGlobalEvents, cancelEvent } from '../../../helper/events'; +import { MynahEventNames, PromptAttachmentType } from '../../../static'; +import { Card } from '../../card/card'; +import { CardBody } from '../../card/card-body'; +import { SyntaxHighlighter } from '../../syntax-highlighter'; +import testIds from '../../../helper/test-ids'; + +export interface PromptTextAttachmentProps { + tabId: string; + content: string; + type: PromptAttachmentType; +} + +export class PromptTextAttachment { + render: ExtendedHTMLElement; + private readonly props: PromptTextAttachmentProps; + private previewOverlay: Overlay | undefined; + constructor (props: PromptTextAttachmentProps) { + this.props = props; + this.render = new Card({ + testId: testIds.prompt.attachment, + padding: 'none', + border: false, + events: { + mouseenter: () => { + this.showPreviewOverLay(); + }, + mouseleave: () => { + this.closePreviewOverLay(); + }, + }, + classNames: [ 'mynah-prompt-attachment-container' ], + children: [ + new CardBody({ + ...(this.props.type === 'markdown' + ? { body: this.props.content } + : { + children: [ new SyntaxHighlighter({ + block: true, + codeStringWithMarkup: this.props.content, + }).render ] + }), + }).render, + new Button({ + testId: testIds.prompt.attachmentRemove, + classNames: [ 'code-snippet-close-button' ], + onClick: e => { + cancelEvent(e); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.REMOVE_ATTACHMENT, this.props.tabId); + this.closePreviewOverLay(); + }, + icon: new Icon({ icon: MynahIcons.CANCEL }).render, + primary: false, + }).render, + ], + }).render; + } + + private readonly showPreviewOverLay = (): void => { + this.previewOverlay = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: this.render, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + new Card({ + border: false, + classNames: [ 'mynah-prompt-input-snippet-attachment-overlay' ], + children: [ + new CardBody({ + ...(this.props.type === 'markdown' + ? { body: this.props.content } + : { + children: [ new SyntaxHighlighter({ + block: true, + codeStringWithMarkup: this.props.content, + }).render ] + }), + }).render, + ] + }).render + ], + }); + }; + + private readonly closePreviewOverLay = (): void => { + if (this.previewOverlay !== undefined) { + this.previewOverlay.close(); + this.previewOverlay = undefined; + } + }; + + public readonly clear = (): void => { + this.closePreviewOverLay(); + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-text-input.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-text-input.ts new file mode 100644 index 00000000..b9ebdeee --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-text-input.ts @@ -0,0 +1,759 @@ +import { Config } from '../../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../../helper/dom'; +import { MynahUIGlobalEvents } from '../../../helper/events'; +import { MynahUITabsStore } from '../../../helper/tabs-store'; +import { MynahEventNames, QuickActionCommand, QuickActionCommandGroup } from '../../../static'; +import { MAX_USER_INPUT } from '../chat-prompt-input'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../../overlay'; +import { Card } from '../../card/card'; +import { CardBody } from '../../card/card-body'; +import testIds from '../../../helper/test-ids'; +import { generateUID } from '../../../main'; +import { Icon, MynahIcons } from '../../icon'; + +const PREVIEW_DELAY = 500; +const IMAGE_CONTEXT_SELECT_KEYWORD = '@image:'; +export interface PromptTextInputProps { + tabId: string; + initMaxLength: number; + children?: ExtendedHTMLElement[]; + onKeydown: (e: KeyboardEvent) => void; + onInput?: (e: KeyboardEvent) => void; + onFocus?: () => void; + onBlur?: () => void; +} + +export class PromptTextInput { + render: ExtendedHTMLElement; + promptTextInputMaxLength: number; + private lastCursorIndex: number = 0; + private readonly props: PromptTextInputProps; + private readonly promptTextInput: ExtendedHTMLElement; + private promptInputOverlay: Overlay | null = null; + private keydownSupport: boolean = true; + private readonly selectedContext: Record = {}; + private contextTooltip: Overlay | null; + private contextTooltipTimeout: ReturnType; + private mutationObserver: MutationObserver | null = null; + + constructor (props: PromptTextInputProps) { + this.props = props; + this.promptTextInputMaxLength = props.initMaxLength; + + const initialDisabledState = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputDisabledState') as boolean; + + this.promptTextInput = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.input, + classNames: [ 'mynah-chat-prompt-input', 'empty' ], + innerHTML: '', + attributes: { + contenteditable: 'plaintext-only', + ...(initialDisabledState ? { disabled: 'disabled' } : {}), + tabindex: '0', + rows: '1', + maxlength: MAX_USER_INPUT().toString(), + type: 'text', + placeholder: MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputPlaceholder'), + ...(Config.getInstance().config.autoFocus ? { autofocus: 'autofocus' } : {}) + }, + events: { + keypress: (e: KeyboardEvent) => { + if (!this.keydownSupport) { + this.props.onKeydown(e); + } + }, + keydown: (e: KeyboardEvent) => { + if (e.key !== '') { + this.keydownSupport = true; + this.props.onKeydown(e); + } else { + this.keydownSupport = false; + } + this.hideContextTooltip(); + }, + keyup: (e: KeyboardEvent) => { + this.lastCursorIndex = this.updateCursorPos(); + + // Check if image command exists in context commands to make the feature consistent + const contextCommands = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('contextCommands') as QuickActionCommandGroup[] | undefined; + const hasImageCommand = contextCommands?.some(group => + group.commands.some(cmd => cmd.command.toLowerCase() === 'image') + ); + + if (hasImageCommand ?? false) { + const text = this.promptTextInput.textContent ?? ''; + if (text.includes(IMAGE_CONTEXT_SELECT_KEYWORD)) { + // Dispatch event to open file system + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.OPEN_FILE_SYSTEM, { + tabId: this.props.tabId, + type: 'image', + insertPosition: this.lastCursorIndex - IMAGE_CONTEXT_SELECT_KEYWORD.length + }); + + // Remove the trigger text + const selection = window.getSelection(); + if ((selection?.rangeCount) != null) { + const range = selection.getRangeAt(0); + const textNodes = Array.from(this.promptTextInput.childNodes).filter((node): node is Text => node.nodeType === Node.TEXT_NODE); + + // Find the node containing "@image:" + for (const node of textNodes) { + const nodeText = node.textContent ?? ''; + const imageTagIndex = nodeText.indexOf(IMAGE_CONTEXT_SELECT_KEYWORD); + + if (imageTagIndex !== -1) { + // Create a range that selects "@image:" + range.setStart(node, imageTagIndex); + range.setEnd(node, imageTagIndex + IMAGE_CONTEXT_SELECT_KEYWORD.length); + range.deleteContents(); + break; + } + } + } + } + } + }, + input: (e: KeyboardEvent) => { + if (this.props.onInput !== undefined) { + this.props.onInput(e); + } + this.removeContextPlaceholderOverlay(); + this.checkIsEmpty(); + }, + focus: () => { + if (typeof this.props.onFocus !== 'undefined') { + this.props.onFocus(); + } + this.lastCursorIndex = this.updateCursorPos(); + }, + blur: () => { + if (typeof this.props.onBlur !== 'undefined') { + this.props.onBlur(); + } + }, + paste: (e: ClipboardEvent): void => { + // Prevent the default paste behavior + e.preventDefault(); + + // Get plain text from clipboard + const text = e.clipboardData?.getData('text/plain'); + if (text != null) { + // Insert text at cursor position + const selection = window.getSelection(); + if ((selection?.rangeCount) != null) { + const range = selection.getRangeAt(0); + range.deleteContents(); + range.insertNode(document.createTextNode(text)); + + // Move cursor to end of inserted text + range.collapse(false); + selection.removeAllRanges(); + selection.addRange(range); + } + + // Check if input is empty and trigger input event + this.checkIsEmpty(); + if (this.props.onInput != null) { + this.props.onInput(new KeyboardEvent('input')); + } + } + }, + }, + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.inputWrapper, + classNames: [ 'mynah-chat-prompt-input-inner-wrapper', 'no-text' ], + children: [ + ...(this.props.children ?? []), + this.promptTextInput, + ] + }); + + // Set up MutationObserver to detect context span removals + this.setupContextRemovalObserver(); + + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).subscribe('promptInputDisabledState', (isDisabled: boolean) => { + if (isDisabled) { + this.promptTextInput.setAttribute('disabled', 'disabled'); + this.promptTextInput.setAttribute('contenteditable', 'false'); + this.promptTextInput.blur(); + } else { + // Enable the input field and focus on it + this.promptTextInput.removeAttribute('disabled'); + this.promptTextInput.setAttribute('contenteditable', 'plaintext-only'); + if (Config.getInstance().config.autoFocus && document.hasFocus()) { + this.promptTextInput.focus(); + } + } + }); + + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).subscribe('promptInputPlaceholder', (placeholderText: string) => { + if (placeholderText !== undefined) { + this.promptTextInput.update({ + attributes: { + placeholder: placeholderText + } + }); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.ADD_CUSTOM_CONTEXT, (data: { tabId: string; contextCommands: QuickActionCommand[]; insertPosition?: number}) => { + if (data.tabId === this.props.tabId) { + let insertPos = data.insertPosition ?? this.lastCursorIndex; + data.contextCommands.forEach((command) => { + this.insertContextItem(command, insertPos); + insertPos = this.getCursorPos(); + }); + this.focus(); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.TAB_FOCUS, (data) => { + if (data.tabId === this.props.tabId) { + this.promptTextInput.focus(); + } + }); + + this.clear(); + } + + private readonly setupContextRemovalObserver = (): void => { + if (MutationObserver != null) { + this.mutationObserver = new MutationObserver((mutations) => { + let contextRemoved = false; + const removedContextIds: string[] = []; + + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + mutation.removedNodes.forEach((node) => { + if (node.nodeType === Node.ELEMENT_NODE) { + const element = node as Element; + if (element.classList.contains('context')) { + const contextId = element.getAttribute('context-tmp-id'); + if (contextId != null && contextId !== '') { + removedContextIds.push(contextId); + contextRemoved = true; + } + } + // Also check for context spans within removed nodes + const contextSpans = element.querySelectorAll('.context'); + contextSpans.forEach((span) => { + const contextId = span.getAttribute('context-tmp-id'); + if (contextId != null && contextId !== '') { + removedContextIds.push(contextId); + contextRemoved = true; + } + }); + } + }); + } + }); + + if (contextRemoved) { + this.handleContextRemoval(removedContextIds); + } + }); + + this.mutationObserver.observe(this.promptTextInput, { + childList: true, + subtree: true + }); + } + }; + + private readonly handleContextRemoval = (removedContextIds: string[]): void => { + const currentCustomContext = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('customContextCommand') as QuickActionCommand[] ?? []; + const removedContexts: QuickActionCommand[] = []; + + // Find the removed contexts from our selectedContext map + removedContextIds.forEach((contextId) => { + const removedContext = this.selectedContext[contextId]; + if (removedContext != null) { + removedContexts.push(removedContext); + } + }); + + // Clean up the selectedContext map by creating a new object without the removed keys + const updatedSelectedContext = Object.fromEntries( + Object.entries(this.selectedContext).filter(([ key ]) => !removedContextIds.includes(key)) + ); + Object.assign(this.selectedContext, updatedSelectedContext); + + // Remove the contexts from the data store + if (removedContexts.length > 0) { + const updatedCustomContext = currentCustomContext.filter((context) => { + return !removedContexts.some((removed) => + removed.command === context.command && + removed.icon === context.icon && + removed.description === context.description + ); + }); + + MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).updateStore({ + customContextCommand: updatedCustomContext + }); + } + }; + + public readonly restoreRange = (range: Range): void => { + const selection = window.getSelection(); + if (selection != null) { + selection.removeAllRanges(); + selection.addRange(range); + this.updateCursorPos(); + } + }; + + private readonly updateCursorPos = (): number => { + const selection = window.getSelection(); + if ((selection == null) || (selection.rangeCount === 0)) return 0; + + const range = selection.getRangeAt(0); + const container = this.promptTextInput; + + // If the selection is not within our container, return 0 + if (!container.contains(range.commonAncestorContainer)) return 0; + + // Get the range from start of container to cursor position + const preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(container); + preCaretRange.setEnd(range.endContainer, range.endOffset); + + return preCaretRange.toString().length; + }; + + private readonly updateSelectionAndCursor = (range: Range, maintainCursor: boolean = false, selection?: Selection | null): void => { + const sel = selection ?? window.getSelection(); + if (!maintainCursor && sel != null) { + sel.removeAllRanges(); + sel.addRange(range); + this.lastCursorIndex = this.updateCursorPos(); + } + }; + + private readonly insertSpaceNodeAndUpdateCursor = (element: HTMLElement | Text, maintainCursor: boolean = false, selection?: Selection | null): void => { + const spaceNode = document.createTextNode('\u00A0'); + element.parentNode?.insertBefore(spaceNode, element.nextSibling); + if (!maintainCursor && selection != null) { + const endRange = document.createRange(); + endRange.setStartAfter(spaceNode); + endRange.collapse(true); + this.updateSelectionAndCursor(endRange, maintainCursor, selection); + } + }; + + private readonly checkIsEmpty = (): void => { + if (this.promptTextInput.textContent === '' && this.promptTextInput.querySelectorAll('span.context').length === 0) { + this.promptTextInput.addClass('empty'); + this.render.addClass('no-text'); + } else { + this.promptTextInput.removeClass('empty'); + this.render.removeClass('no-text'); + } + }; + + private readonly removeContextPlaceholderOverlay = (): void => { + this.promptInputOverlay?.close(); + this.promptInputOverlay?.render.remove(); + this.promptInputOverlay = null; + }; + + private readonly insertElementToGivenPosition = ( + element: HTMLElement | Text, + position: number, + endPosition?: number, + maintainCursor: boolean = false + ): void => { + const selection = window.getSelection(); + // Clamp position to total content length + let totalLength = 0; + for (const node of this.promptTextInput.childNodes) { + totalLength += node.textContent?.length ?? 0; + } + const safePosition = Math.min(position, totalLength); + + if (this.promptTextInput.childNodes.length === 0) { + this.promptTextInput.insertChild('beforeend', element as HTMLElement); + this.insertSpaceNodeAndUpdateCursor(element, maintainCursor, selection); + return; + } + + // If the only child is a
, treat as empty and insert at end + // The
is inserted into contenteditable html element if left empty, + // this happens when delete prompt input by selecting all and clicking delete + if ( + this.promptTextInput.childNodes.length === 1 && + this.promptTextInput.firstChild?.nodeName === 'BR' + ) { + // Remove the br element and insert our element + this.promptTextInput.firstChild.remove(); + this.promptTextInput.insertChild('beforeend', element as HTMLElement); + this.insertSpaceNodeAndUpdateCursor(element, maintainCursor, selection); + return; + } + + if (selection == null || + (selection.focusNode?.isSameNode(this.promptTextInput) === false && + selection.focusNode?.parentElement?.isSameNode(this.promptTextInput) === false)) { + this.promptTextInput.insertChild('beforeend', element as HTMLElement); + return; + } + + // Store original cursor position if we need to maintain it + const originalRange = maintainCursor ? selection.getRangeAt(0).cloneRange() : null; + + const range = document.createRange(); + let currentPos = 0; + let foundInsertionPoint = false; + + // Find the correct text node and offset + for (const node of this.promptTextInput.childNodes) { + const length = node.textContent?.length ?? 0; + + // Only try to set range on text nodes + if (currentPos + length >= safePosition && (node.nodeType === Node.TEXT_NODE)) { + const offset = Math.min(safePosition - currentPos, length); + // Defensive: only setStart if offset is valid + if (offset >= 0 && offset <= length) { + range.setStart(node, offset); + + if (endPosition != null) { + let endNode = node; + let endOffset = Math.min(endPosition - currentPos, length); + + if (endPosition > currentPos + length) { + let endPos = currentPos + length; + for (let i = Array.from(this.promptTextInput.childNodes).indexOf(node) + 1; + i < this.promptTextInput.childNodes.length; + i++) { + const nextNode = this.promptTextInput.childNodes[i]; + const nextLength = nextNode.textContent?.length ?? 0; + + if (endPos + nextLength >= endPosition) { + endNode = nextNode; + endOffset = endPosition - endPos; + break; + } + endPos += nextLength; + } + } + + range.setEnd(endNode, endOffset); + range.deleteContents(); + } + + range.insertNode(element); + + if (endPosition != null) { + const spaceNode = document.createTextNode('\u00A0'); + range.setStartAfter(element); + range.insertNode(spaceNode); + range.setStartAfter(spaceNode); + element = spaceNode; + } else { + range.setStartAfter(element); + } + foundInsertionPoint = true; + break; + } + } + currentPos += length; + } + + // Fallback: if nothing was inserted, insert at the end + if (!foundInsertionPoint) { + this.promptTextInput.insertChild('beforeend', element as HTMLElement); + this.insertSpaceNodeAndUpdateCursor(element, maintainCursor, selection); + return; + } + + if (!maintainCursor) { + // Only modify cursor position if maintainCursor is false + range.collapse(true); + this.updateSelectionAndCursor(range, maintainCursor, selection); + } else if (originalRange != null) { + // Restore original cursor position + selection.removeAllRanges(); + selection.addRange(originalRange); + } + }; + + private readonly moveCursorToEnd = (): void => { + const range = document.createRange(); + range.selectNodeContents(this.promptTextInput); + range.collapse(false); + const selection = window.getSelection(); + if (selection != null) { + selection.removeAllRanges(); + selection.addRange(range); + } + }; + + private readonly showContextTooltip = (e: MouseEvent, contextItem: QuickActionCommand): void => { + clearTimeout(this.contextTooltipTimeout); + this.contextTooltipTimeout = setTimeout(() => { + const elm: HTMLElement = e.target as HTMLElement; + this.contextTooltip = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.contextTooltip, + classNames: [ 'mynah-chat-prompt-context-tooltip' ], + children: [ + ...(contextItem.icon !== undefined + ? [ + new Icon({ + icon: contextItem.icon + }).render + ] + : []), + { + type: 'div', + classNames: [ 'mynah-chat-prompt-context-tooltip-container' ], + children: [ + { + type: 'div', + classNames: [ 'mynah-chat-prompt-context-tooltip-name' ], + children: [ contextItem.command ] + }, + ...(contextItem.description !== undefined + ? [ { + type: 'div', + classNames: [ 'mynah-chat-prompt-context-tooltip-description' ], + children: [ contextItem.description ] + } ] + : []) + ] + } + ] + }) + ], + }); + }, PREVIEW_DELAY); + }; + + private readonly hideContextTooltip = (): void => { + if (this.contextTooltipTimeout !== null) { + clearTimeout(this.contextTooltipTimeout); + } + if (this.contextTooltip != null) { + this.contextTooltip.close(); + this.contextTooltip = null; + } + }; + + public readonly insertContextItem = (contextItem: QuickActionCommand, position: number, topBarHidden?: boolean): void => { + const temporaryId = generateUID(); + this.selectedContext[temporaryId] = contextItem; + const contextSpanElement = DomBuilder.getInstance().build({ + type: 'span', + children: [ + ...(topBarHidden !== true ? [ new Icon({ icon: MynahIcons.PIN, classNames: [ 'hover-icon' ] }).render ] : []), + new Icon({ icon: contextItem.icon ?? MynahIcons.AT }).render, + { type: 'span', classNames: [ 'at-char' ], innerHTML: '@' }, + `${contextItem.command.replace(/^@?(.*)$/, '$1')}` + ], + classNames: [ 'context', topBarHidden === true ? 'no-hover' : '' ], + attributes: { + 'context-tmp-id': temporaryId, + contenteditable: 'false' + }, + events: { + mouseenter: (e) => { + this.showContextTooltip(e, contextItem); + }, + mouseleave: () => { + this.hideContextTooltip(); + }, + click: () => { + this.hideContextTooltip(); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CONTEXT_PINNED, { tabId: this.props.tabId, contextItem }); + } + } + }); + this.insertElementToGivenPosition(contextSpanElement, position, this.getCursorPos()); + + if (contextItem.placeholder != null) { + this.promptInputOverlay = new Overlay({ + background: true, + closeOnOutsideClick: true, + referenceElement: contextSpanElement ?? this.render, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + new Card({ + border: false, + children: [ + new CardBody({ + body: contextItem.placeholder + }).render + ] + }).render + ], + }); + } + + this.checkIsEmpty(); + }; + + public readonly getCursorPos = (): number => this.lastCursorIndex; + + public readonly clear = (): void => { + this.promptTextInput.innerHTML = ''; + const defaultPlaceholder = MynahUITabsStore.getInstance().getTabDataStore(this.props.tabId).getValue('promptInputPlaceholder'); + this.updateTextInputPlaceholder(defaultPlaceholder); + this.removeContextPlaceholderOverlay(); + this.checkIsEmpty(); + }; + + public readonly focus = (): void => { + if (Config.getInstance().config.autoFocus) { + this.promptTextInput.focus(); + } + this.moveCursorToEnd(); + }; + + public readonly blur = (): void => { + this.promptTextInput.blur(); + this.checkIsEmpty(); + }; + + public readonly getTextInputValue = (withInputLineBreaks?: boolean): string => { + if (withInputLineBreaks === true) { + return (this.promptTextInput.innerText ?? '').trim(); + } + return (this.promptTextInput.textContent ?? '').trim(); + }; + + public readonly updateTextInputValue = (value: string): void => { + this.promptTextInput.innerText = value; + this.checkIsEmpty(); + }; + + public readonly insertEndSpace = (): void => { + this.promptTextInput.insertAdjacentHTML('beforeend', ' '); + }; + + public readonly updateTextInputMaxLength = (maxLength: number): void => { + this.promptTextInputMaxLength = maxLength; + this.promptTextInput.update({ + attributes: { + maxlength: maxLength.toString(), + } + }); + }; + + public readonly updateTextInputPlaceholder = (text: string): void => { + this.promptTextInput.update({ + attributes: { + placeholder: text, + } + }); + }; + + public readonly deleteTextRange = (position: number, endPosition: number): void => { + const selection = window.getSelection(); + if (selection == null) return; + + const range = document.createRange(); + let currentPos = 0; + let startNode = null; + let startOffset = 0; + let endNode = null; + let endOffset = 0; + + // Find start and end positions + for (const node of this.promptTextInput.childNodes) { + const length = node.textContent?.length ?? 0; + + // Find start position + if ((startNode == null) && currentPos + length >= position) { + startNode = node; + startOffset = position - currentPos; + } + + // Find end position + if (currentPos + length >= endPosition) { + endNode = node; + endOffset = endPosition - currentPos; + break; + } + + currentPos += length; + } + + // If we found both positions, delete the range + if ((startNode != null) && (endNode != null)) { + range.setStart(startNode, startOffset); + range.setEnd(endNode, endOffset); + range.deleteContents(); + } + + this.checkIsEmpty(); + }; + + /** + * Returns the cursorLine, totalLines and if the cursor is at the beginning or end of the whole text + * @returns {cursorLine: number, totalLines: number, isAtTheBeginning: boolean, isAtTheEnd: boolean} + */ + public readonly getCursorPosition = (): { cursorLine: number; totalLines: number; isAtTheBeginning: boolean; isAtTheEnd: boolean } => { + const lineHeight = parseFloat(window.getComputedStyle(this.promptTextInput, null).getPropertyValue('line-height')); + let isAtTheBeginning = false; + let isAtTheEnd = false; + let cursorLine = -1; + const cursorElm = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'cursor' ] + }) as HTMLSpanElement; + this.insertElementToGivenPosition(cursorElm, this.getCursorPos(), undefined, true); + cursorLine = Math.floor((cursorElm.offsetTop + (cursorElm.offsetHeight)) / lineHeight) ?? 0; + if (cursorLine <= 1 && (cursorElm?.offsetLeft ?? 0) === 0) { + isAtTheBeginning = true; + } + + const eolElm = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'eol' ] + }) as HTMLSpanElement; + this.promptTextInput.insertChild('beforeend', eolElm); + const totalLines = Math.floor((eolElm.offsetTop + (eolElm.offsetHeight)) / lineHeight) ?? 0; + if (cursorElm.offsetLeft === eolElm.offsetLeft && cursorElm.offsetTop === eolElm.offsetTop) { + isAtTheEnd = true; + } + + cursorElm.remove(); + eolElm.remove(); + + return { + cursorLine, + totalLines, + isAtTheBeginning, + isAtTheEnd, + }; + }; + + public readonly getUsedContext = (): QuickActionCommand[] => { + return Array.from(this.promptTextInput.querySelectorAll('span.context')).map((context) => { + return this.selectedContext[context.getAttribute('context-tmp-id') ?? ''] ?? {}; + }); + }; + + public readonly destroy = (): void => { + if (this.mutationObserver != null) { + this.mutationObserver.disconnect(); + this.mutationObserver = null; + } + }; +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.ts new file mode 100644 index 00000000..0eebff4e --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-top-bar/prompt-top-bar.ts @@ -0,0 +1,425 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../../../helper/dom'; +import { MynahUIGlobalEvents } from '../../../../helper/events'; +import { convertQuickActionCommandGroupsToDetailedListGroups } from '../../../../helper/quick-pick-data-handler'; +import testIds from '../../../../helper/test-ids'; +import { ChatItemButton, DetailedList, DetailedListItemGroup, MynahEventNames, QuickActionCommand } from '../../../../static'; +import { Button } from '../../../button'; +import { DetailedListWrapper } from '../../../detailed-list/detailed-list'; +import { Icon, MynahIcons } from '../../../icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../../../overlay'; +import { TopBarButton } from './top-bar-button'; + +const PREVIEW_DELAY = 500; + +export interface PromptTopBarProps { + classNames?: string[]; + tabId: string; + contextItems?: QuickActionCommand[]; + title?: string; + + onTopBarTitleClick?: () => void; + onContextItemAdd?: (contextItems: QuickActionCommand) => void; + onContextItemRemove?: (contextItems: QuickActionCommand) => void; + + topBarButton?: ChatItemButton; + onTopBarButtonClick?: (action: ChatItemButton) => void; +} + +export class PromptTopBar { + render: ExtendedHTMLElement; + visibleCount: number; + overflowOverlay?: Overlay; + topBarButton: TopBarButton; + overflowListContainer: DetailedListWrapper; + private contextTooltip: Overlay | null; + private contextTooltipTimeout: ReturnType; + private readonly props: PromptTopBarProps; + private titleButton: Button; + private overflowButton: ExtendedHTMLElement; + + constructor (props: PromptTopBarProps) { + this.props = props; + this.visibleCount = (this.props.contextItems != null) ? this.props.contextItems?.length : 0; + + this.topBarButton = new TopBarButton({ + topBarButton: this.props.topBarButton, + onTopBarButtonClick: this.props.onTopBarButtonClick + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.topBar, + classNames: [ 'mynah-prompt-input-top-bar', ...(this.props.classNames ?? []), this.isHidden() ? 'hidden' : '' ], + children: [ + this.generateTitle(), + ...this.generateContextPills(), + this.generateOverflowPill(), + this.topBarButton.render + ], + + }); + + // Add resize observer to handle responsive behavior + this.setupResizeObserver(); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CONTEXT_PINNED, (data) => { + if (data.tabId === props.tabId) { + this.addContextPill(data.contextItem); + } + }); + + // Use setTimeout to ensure the DOM is fully rendered before measuring + // TODO: Switch to an IntersectionObserver + setTimeout(() => { + this.recalculateVisibleItems(); + }, 100); + } + + update (newProps?: Partial): void { + if (newProps?.contextItems != null) { + this.props.contextItems = newProps.contextItems; + } + if (newProps?.title != null) { + this.props.title = newProps.title; + } + + if (newProps?.topBarButton != null) { + this.topBarButton.update({ topBarButton: newProps.topBarButton }); + } + + this.render.update({ + children: [ + this.generateTitle(), + ...this.generateContextPills(), + this.generateOverflowPill(), + this.topBarButton.render + ] + + }); + + if (this.isHidden()) { + this.render.addClass('hidden'); + } else { + this.render.removeClass('hidden'); + } + + if (newProps?.contextItems != null || newProps?.title != null) { + this.recalculateVisibleItems(); + } + } + + updateTopBarButtonOverlay (topBarButtonOverlay: DetailedList): void { + this.topBarButton.onTopBarButtonOverlayChanged(topBarButtonOverlay); + } + + isHidden (): boolean { + return this.props.title == null || this.props.title.length === 0; + } + + generateTitle (): ExtendedHTMLElement | string { + const { title } = this.props; + if (title == null) { + return ''; + } + if (this.titleButton == null) { + this.titleButton = new Button({ + onClick: () => { + this.props.onTopBarTitleClick?.(); + }, + primary: false, + status: 'clear', + border: false, + label: title, + hidden: title == null + }); + } else { + this.titleButton.updateLabel(title); + } + return this.titleButton.render; + } + + getVisibleContextItems (): QuickActionCommand[] { + return this.props.contextItems?.slice(0, this.visibleCount) ?? []; + } + + getOverflowContextItems (): QuickActionCommand[] { + return this.props.contextItems?.slice(this.visibleCount) ?? []; + } + + generateContextPills (): Array { + if ((this.props.contextItems != null) && this.props.contextItems?.length > 0) { + return this.getVisibleContextItems().map((contextItem) => { + return DomBuilder.getInstance().build({ + type: 'span', + testId: testIds.prompt.topBarContextPill, + children: [ + new Icon({ icon: MynahIcons.CANCEL, classNames: [ 'hover-icon' ] }).render, + new Icon({ icon: contextItem.icon ?? MynahIcons.AT }).render, + { type: 'span', classNames: [ 'label' ], children: [ `${contextItem.command.replace(/^@?(.*)$/, '$1')}` ] } + ], + classNames: [ 'pinned-context-pill' ], + attributes: { + contenteditable: 'false' + }, + events: { + mouseenter: (e) => { + this.showContextTooltip(e, contextItem); + }, + mouseleave: (e) => { + this.hideContextTooltip(); + }, + click: (e) => { + this.hideContextTooltip(); + this.removeContextPill(contextItem.id ?? contextItem.command); + } + } + }); + }); + } + return []; + } + + private readonly showContextTooltip = (e: MouseEvent, contextItem: QuickActionCommand): void => { + clearTimeout(this.contextTooltipTimeout); + this.contextTooltipTimeout = setTimeout(() => { + const elm: HTMLElement = e.target as HTMLElement; + + this.contextTooltip = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.topBarContextTooltip, + classNames: [ 'mynah-chat-prompt-context-tooltip' ], + children: [ + ...(contextItem.icon !== undefined + ? [ + new Icon({ + icon: contextItem.icon + }).render + ] + : []), + { + type: 'div', + classNames: [ 'mynah-chat-prompt-context-tooltip-container' ], + children: [ + { + type: 'div', + classNames: [ 'mynah-chat-prompt-context-tooltip-name' ], + children: [ contextItem.command ] + }, + ...(contextItem.description !== undefined + ? [ { + type: 'div', + classNames: [ 'mynah-chat-prompt-context-tooltip-description' ], + children: [ contextItem.description ] + } ] + : []) + ] + } + ] + }) + ], + }); + }, PREVIEW_DELAY); + }; + + private readonly hideContextTooltip = (): void => { + if (this.contextTooltipTimeout !== null) { + clearTimeout(this.contextTooltipTimeout); + } + if (this.contextTooltip != null) { + this.contextTooltip.close(); + this.contextTooltip = null; + } + }; + + removeContextPill (id: string): void { + const itemToRemove = this.props.contextItems?.find((item) => (item.id ?? item.command) === id); + if (itemToRemove != null) { + this.props.contextItems = this.props.contextItems?.filter((item) => (item.id ?? item.command) !== id); + this.props.onContextItemRemove?.(itemToRemove); + this.update(); + this.recalculateVisibleItems(); + this.overflowOverlay?.updateContent([ this.generateOverflowOverlayChildren() ]); + } + } + + addContextPill (contextItem: QuickActionCommand): void { + if (this.props.contextItems?.find(({ id }) => id != null && id === contextItem.id) == null) { + this.props.contextItems?.push(contextItem); + this.props.onContextItemAdd?.(contextItem); + this.update(); + this.recalculateVisibleItems(); + } + } + + getOverflowCount (): number { + return (this.props.contextItems?.length ?? 0) - this.visibleCount; + } + + generateOverflowPill (): ExtendedHTMLElement | string { + if (this.getOverflowCount() <= 0) { + return ''; + } + if (this.overflowButton == null) { + this.overflowButton = DomBuilder.getInstance().build({ + type: 'span', + testId: testIds.prompt.topBarOverflowPill, + children: [ + `+${this.getOverflowCount()}` + ], + classNames: [ 'pinned-context-pill', 'overflow-button' ], + attributes: { + contenteditable: 'false' + }, + events: { + click: (e: Event) => { + this.showOverflowOverlay(e); + } + } + }); + } else { + this.overflowButton.update({ + children: [ + `+${this.getOverflowCount()}` + ] + }); + } + + return this.overflowButton; + } + + showOverflowOverlay (e: Event): void { + if (this.overflowOverlay == null) { + this.overflowOverlay = new Overlay({ + testId: testIds.prompt.topBarOverflowOverlay, + background: true, + closeOnOutsideClick: true, + referenceElement: this.overflowButton, + removeIfReferenceElementRemoved: false, + dimOutside: false, + onClose: () => { this.overflowOverlay = undefined; }, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.END_TO_LEFT, + children: [ this.generateOverflowOverlayChildren() ] + }); + } else { + this.overflowOverlay.updateContent([ this.generateOverflowOverlayChildren() ]); + } + } + + generateOverflowOverlayChildren (): ExtendedHTMLElement { + const overflowItems = this.getOverflowItemsAsDetailedListGroup(); + if (this.overflowListContainer == null) { + this.overflowListContainer = new DetailedListWrapper({ + detailedList: { list: overflowItems, selectable: 'clickable' }, + onItemActionClick: (_, item) => { + const itemId = item?.id ?? item?.title; + if (itemId != null) { this.removeContextPill(itemId); } + }, + onItemClick: (item) => { + const itemId = item.id ?? item.title; + if (itemId != null) { this.removeContextPill(itemId); } + }, + }); + } else { + if (overflowItems.length === 0 || overflowItems[0].children?.length === 0) { + this.overflowOverlay?.close(); + } else { + this.overflowListContainer.update({ list: overflowItems }); + } + } + return DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-prompt-quick-picks-overlay-wrapper' ], + children: [ this.overflowListContainer.render ] + }); ; + } + + getOverflowItemsAsDetailedListGroup (): DetailedListItemGroup[] { + return convertQuickActionCommandGroupsToDetailedListGroups([ { commands: this.getOverflowContextItems() } ]).map((group) => ({ + ...group, + children: group.children?.map((child) => ({ ...child, actions: [ { icon: MynahIcons.CANCEL, id: 'remove' } ] })) + })); + } + + private setupResizeObserver (): void { + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.ROOT_RESIZE, () => { + this.recalculateVisibleItems(); + }); + } + + // Sets visibleContextItems based on container width. Pills that don't fit will be moved into context overflow. + // As width increases, move items back from context overflow into row of displayed pills. + private recalculateVisibleItems (): void { + const { contextItems } = this.props; + if ((contextItems == null) || contextItems.length === 0) return; + + const containerWidth = this.render.offsetWidth; + const titleWidth = (this.titleButton != null) ? this.titleButton.render.offsetWidth + 8 : 0; // 8px for margin/padding + const topBarButtonWidth = (this.topBarButton != null) ? this.topBarButton.render.offsetWidth + 8 : 0; + + // Available width for context pills + const availableWidth = containerWidth - titleWidth - topBarButtonWidth - 16; // 16px for container padding + + // Check if we need to handle width increase scenario + const shouldCheckForExpansion = this.getOverflowCount() > 0; + if (shouldCheckForExpansion) { + // Try to add one more item from overflow if we have at least 100px of extra space + const extraSpaceNeeded = 100; // Maximum width for a pill to be brought back + + // Calculate current used width + let currentUsedWidth = 0; + const currentPills = Array.from(this.render.querySelectorAll('.pinned-context-pill')); + currentPills.forEach((pill) => { + currentUsedWidth += (pill as HTMLElement).offsetWidth + 8; // 8px for gap + }); + + // Check if we have enough space to bring back an item from overflow + const remainingWidth = availableWidth - currentUsedWidth; + if (remainingWidth >= extraSpaceNeeded && this.getOverflowCount() > 0) { + // We have enough space to bring back at least one item + this.visibleCount++; + + // Rebuild the component with the new visible items + this.update(); + return; // Exit early as we've updated the component + } + } + + // Handle width decrease scenario + // Get all context pills + const contextPills = Array.from(this.render.querySelectorAll('.pinned-context-pill:not(.overflow-button)')); + + // Calculate how many pills can fit + let usedWidth = 0; + let visibleCount = 0; + + for (let i = 0; i < contextPills.length; i++) { + const pill = contextPills[i] as HTMLElement; + usedWidth += pill.offsetWidth + 8; // 8px for gap + + if (usedWidth > availableWidth) { + break; + } + + visibleCount++; + } + // If we need to adjust the visible items + if (this.visibleCount !== visibleCount) { + this.visibleCount = visibleCount; + + // Rebuild the component with the new visible items + + this.update(); + } + } +} diff --git a/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-top-bar/top-bar-button.ts b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-top-bar/top-bar-button.ts new file mode 100644 index 00000000..3511127f --- /dev/null +++ b/vendor/mynah-ui/src/components/chat-item/prompt-input/prompt-top-bar/top-bar-button.ts @@ -0,0 +1,131 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../../../helper/dom'; +import testIds from '../../../../helper/test-ids'; +import { DetailedList, DetailedListItem, ChatItemButton } from '../../../../static'; +import { Button } from '../../../button'; +import { DetailedListWrapper } from '../../../detailed-list/detailed-list'; +import { Icon } from '../../../icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../../../overlay'; + +export interface TopBarButtonOverlayProps { + tabId: string; + topBarButtonOverlay: DetailedList; + events?: { + onKeyPress?: (e: KeyboardEvent) => void; + onGroupClick?: (groupName: string) => void; + onItemClick?: (detailedListItem: DetailedListItem) => void; + onClose?: () => void; + }; +} + +export interface TopBarButtonProps { + topBarButton?: ChatItemButton; + onTopBarButtonClick?: (action: ChatItemButton) => void; +} + +export class TopBarButton { + render: ExtendedHTMLElement; + private readonly props: TopBarButtonProps; + private overlay?: Overlay; + private checklistSelectorContainer: DetailedListWrapper; + private overlayData: TopBarButtonOverlayProps; + private topBarButton: Button; + private keyPressHandler: (e: KeyboardEvent) => void; + + constructor (props: TopBarButtonProps) { + this.props = props; + + this.render = DomBuilder.getInstance().build({ + testId: testIds.prompt.topBarButton, + type: 'span', + children: this.getTopBarButtonChildren(), + classNames: [ 'top-bar-button' ], + attributes: { + contenteditable: 'false' + }, + }); + } + + update (newProps: TopBarButtonProps): void { + if (newProps.topBarButton != null) { + this.props.topBarButton = newProps.topBarButton; + } + this.render.update({ + children: this.getTopBarButtonChildren(), + }); + } + + closeOverlay (): void { + this.overlay?.close(); + } + + showOverlay (topBarButtonOverlay: TopBarButtonOverlayProps): void { + this.overlayData = topBarButtonOverlay; + + if (this.overlay == null) { + this.keyPressHandler = (e: KeyboardEvent): void => { + this.overlayData.events?.onKeyPress?.(e); + }; + + this.overlay = new Overlay({ + testId: testIds.prompt.topBarActionOverlay, + background: true, + closeOnOutsideClick: true, + referenceElement: this.topBarButton.render, + dimOutside: false, + onClose: () => { + this.overlay = undefined; this.overlayData.events?.onClose?.(); window.removeEventListener('keydown', this.keyPressHandler); + }, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.END_TO_LEFT, + children: [ this.getItemGroups() ] + }); + window.addEventListener('keydown', this.keyPressHandler); + } else { + this.overlay.updateContent([ this.getItemGroups() ]); + } + } + + getTopBarButtonChildren (): Array { + this.topBarButton = new Button({ + onClick: () => { + if (this.props.topBarButton != null) this.props.onTopBarButtonClick?.(this.props.topBarButton); + }, + primary: false, + status: 'clear', + border: false, + icon: this.props.topBarButton?.icon ? new Icon({ icon: this.props.topBarButton.icon }).render : undefined, + label: this.props.topBarButton?.text ?? '', + hidden: this.props.topBarButton == null + }); + + return [ this.topBarButton.render ]; + } + + private readonly getItemGroups = (): ExtendedHTMLElement => { + if (this.checklistSelectorContainer == null) { + this.checklistSelectorContainer = new DetailedListWrapper({ + detailedList: this.overlayData.topBarButtonOverlay, + onGroupClick: this.overlayData.events?.onGroupClick, + onGroupActionClick: (_, groupName) => { if (groupName != null) this.overlayData.events?.onGroupClick?.(groupName); }, + onItemClick: this.overlayData.events?.onItemClick, + onItemActionClick: (_, detailedListItem) => { if (detailedListItem != null) this.overlayData.events?.onItemClick?.(detailedListItem); }, + }); + } else { + this.checklistSelectorContainer?.update(this.overlayData.topBarButtonOverlay, true); + } + + return DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-chat-prompt-quick-picks-overlay-wrapper' ], + children: [ this.checklistSelectorContainer.render ] + }); + }; + + onTopBarButtonOverlayChanged (topBarButtonOverlay: DetailedList): void { + this.overlayData.topBarButtonOverlay = topBarButtonOverlay; + if (this.overlay != null) { + this.overlay.updateContent([ this.getItemGroups() ]); + } + } +} diff --git a/vendor/mynah-ui/src/components/collapsible-content.ts b/vendor/mynah-ui/src/components/collapsible-content.ts new file mode 100644 index 00000000..fd1757b6 --- /dev/null +++ b/vendor/mynah-ui/src/components/collapsible-content.ts @@ -0,0 +1,91 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// eslint-disable @typescript-eslint/restrict-template-expressions +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../helper/dom'; +import { generateUID } from '../helper/guid'; +import { StyleLoader } from '../helper/style-loader'; +import { Icon, MynahIcons } from './icon'; + +interface CollapsibleContentProps { + title: string | ExtendedHTMLElement | HTMLElement | DomBuilderObject; + testId?: string; + children: Array; + classNames?: string[]; + initialCollapsedState?: boolean; + onCollapseStateChange?: (collapsed: boolean) => void; +} +export class CollapsibleContent { + render: ExtendedHTMLElement; + private readonly props: Required; + private readonly uid: string; + private icon: ExtendedHTMLElement; + constructor (props: CollapsibleContentProps) { + StyleLoader.getInstance().load('components/_collapsible-content.scss'); + this.uid = generateUID(); + this.props = { + initialCollapsedState: true, + onCollapseStateChange: () => {}, + testId: 'mynah-ui-collapsible-content', + classNames: [], + ...props + }; + this.icon = new Icon({ icon: this.props.initialCollapsedState ? MynahIcons.RIGHT_OPEN : MynahIcons.DOWN_OPEN }).render; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: this.props.testId, + classNames: [ 'mynah-collapsible-content-wrapper', ...this.props.classNames ], + children: [ + { + type: 'input', + classNames: [ 'mynah-collapsible-content-checkbox' ], + attributes: { + type: 'checkbox', + name: this.uid, + id: this.uid, + ...(this.props.initialCollapsedState ? { checked: 'checked' } : {}) + }, + events: { + change: (e) => { + const val = e.currentTarget.checked; + const newIcon = new Icon({ + icon: val === true ? MynahIcons.RIGHT_OPEN : MynahIcons.DOWN_OPEN + }).render; + this.icon.replaceWith(newIcon); + this.icon = newIcon; + this.props.onCollapseStateChange(val); + } + }, + }, + { + type: 'label', + classNames: [ 'mynah-collapsible-content-label' ], + attributes: { + for: this.uid, + }, + children: [ + { + type: 'div', + classNames: [ 'mynah-collapsible-content-label-title-wrapper' ], + children: [ + this.icon, + { + type: 'span', + classNames: [ 'mynah-collapsible-content-label-title-text' ], + children: [ this.props.title ] + } + ] + }, + { + type: 'div', + classNames: [ 'mynah-collapsible-content-label-content-wrapper' ], + children: this.props.children + }, + ], + }, + ], + }); + } +} diff --git a/vendor/mynah-ui/src/components/detailed-list/detailed-list-item.ts b/vendor/mynah-ui/src/components/detailed-list/detailed-list-item.ts new file mode 100644 index 00000000..0fb78640 --- /dev/null +++ b/vendor/mynah-ui/src/components/detailed-list/detailed-list-item.ts @@ -0,0 +1,263 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import testIds from '../../helper/test-ids'; +import { ChatItemButton, DetailedListItem } from '../../static'; +import { Button } from '../button'; +import { Icon, MynahIcons } from '../icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; +import { parseMarkdown } from '../../helper/marked'; +import { Card } from '../card/card'; +import { CardBody } from '../card/card-body'; + +const TOOLTIP_DELAY = 350; + +export interface DetailedListItemWrapperProps { + listItem: DetailedListItem; + descriptionTextDirection?: 'ltr' | 'rtl'; + onSelect?: (detailedListItem: DetailedListItem) => void; + onClick?: (detailedListItem: DetailedListItem) => void; + onActionClick?: (action: ChatItemButton, detailedListItem?: DetailedListItem) => void; + onShowActionMenuOverlay?: () => void; + selectable?: boolean; + clickable?: boolean; + textDirection?: 'row' | 'column'; +} + +export class DetailedListItemWrapper { + render: ExtendedHTMLElement; + private tooltipOverlay: Overlay | null; + private tooltipTimeout: ReturnType; + private readonly props: DetailedListItemWrapperProps; + private actionMenuOverlay: Overlay | undefined; + + constructor (props: DetailedListItemWrapperProps) { + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.quickPickItem, + classNames: [ 'mynah-detailed-list-item' ], + attributes: { + disabled: this.props.listItem.disabled ?? 'false', + selectable: this.props.selectable ?? 'true', + clickable: this.props.clickable ?? 'false', + }, + events: { + // Prevent mousedown from stealing focus from the input + mousedown: (e) => { cancelEvent(e); }, + click: (e) => { + cancelEvent(e); + if (this.props.listItem.disabled !== true && this.props.selectable !== false) { + this.props.onSelect?.(this.props.listItem); + } + if (this.props.listItem.disabled !== true && this.props.clickable !== false) { + this.props.onClick?.(this.props.listItem); + } + } + }, + children: [ + ...(this.props.listItem.icon != null + ? [ + { + type: 'div', + classNames: [ 'mynah-detailed-list-icon' ], + children: [ + new Icon({ + icon: this.props.listItem.icon, + status: this.props.listItem.iconForegroundStatus + }).render + ] + } + ] + : []), + { + type: 'div', + classNames: [ 'mynah-detailed-list-item-text', 'mynah-detailed-list-item-text-direction-' + (this.props.textDirection ?? 'row') ], + children: [ + ...(this.props.listItem.title != null || this.props.listItem.name != null + ? [ { + type: 'div', + classNames: [ 'mynah-detailed-list-item-name' ], + innerHTML: this.props.listItem.title ?? this.props.listItem.name + } ] + : []), + ...(this.props.listItem.description != null + ? [ { + type: 'div', + classNames: [ 'mynah-detailed-list-item-description', this.props.descriptionTextDirection ?? 'ltr' ], + innerHTML: `${parseMarkdown(this.props.listItem.description.replace(/ /g, ' ').replace(/\n\s*\n/g, ' '), { includeLineBreaks: false, inline: true })}` + } ] + : []) + ] + }, + ...(this.props.listItem.disabledText != null + ? [ + { + type: 'div', + classNames: [ 'mynah-detailed-list-item-disabled-text' ], + children: [ `(${this.props.listItem.disabledText})` ] + } + ] + : (this.props.listItem.children != null) && this.props.listItem.children.length > 0 + ? [ + { + type: 'div', + classNames: [ 'mynah-detailed-list-item-arrow-icon' ], + children: [ + new Icon({ icon: 'right-open' }).render + ] + } + ] + : []), + ...(this.props.listItem.status != null + ? [ + DomBuilder.getInstance().build({ + type: 'div', + classNames: [ + 'mynah-detailed-list-item-status', + `status-${this.props.listItem.status.status ?? 'default'}`, + ], + children: [ + ...(this.props.listItem.status.text != null ? [ { type: 'span', children: [ this.props.listItem.status.text ] } ] : []), + ...(this.props.listItem.status.icon != null ? [ new Icon({ icon: this.props.listItem.status.icon }).render ] : []), + ], + ...(this.props.listItem.status.description != null + ? { + events: { + mouseover: (e) => { + cancelEvent(e); + this.showTooltip(e.currentTarget, parseMarkdown(this.props.listItem.status?.description ?? '', { includeLineBreaks: true })); + }, + mouseleave: this.hideTooltip + } + } + : {}) + }) + ] + : []), + ...(this.props.listItem.actions != null + ? this.props.listItem.groupActions !== false && this.props.listItem.actions.length > 1 + ? [ { + type: 'div', + classNames: [ 'mynah-detailed-list-item-actions' ], + children: [ new Button({ + testId: testIds.detailedList.actionMenu, + icon: new Icon({ icon: MynahIcons.ELLIPSIS }).render, + primary: false, + onClick: (e) => { + cancelEvent(e); + this.showActionMenuOverlay(this.props.listItem); + }, + }).render ] + } ] + : [ { + type: 'div', + classNames: [ 'mynah-detailed-list-item-actions' ], + children: this.props.listItem.actions.map((action) => this.getActionButton(action, (this.props.listItem.groupActions === false), this.props.listItem)) + } ] + : []), + ] + }); + } + + private readonly showTooltip = (elm: HTMLElement, content: string): void => { + if (content.trim() !== undefined) { + clearTimeout(this.tooltipTimeout); + this.tooltipTimeout = setTimeout(() => { + this.tooltipOverlay = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: false, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.CENTER, + children: [ + new Card({ + border: false, + children: [ + new CardBody({ + body: content + }).render + ] + }).render + ], + }); + }, TOOLTIP_DELAY); + } + }; + + public readonly hideTooltip = (): void => { + clearTimeout(this.tooltipTimeout); + if (this.tooltipOverlay !== null) { + this.tooltipOverlay?.close(); + this.tooltipOverlay = null; + } + }; + + public readonly setFocus = (isFocused: boolean, scrollIntoView: boolean): void => { + if (isFocused) { + this.render.addClass('target-command'); + if (scrollIntoView) { + this.render.scrollIntoView(true); + } + } else { + this.render.removeClass('target-command'); + } + }; + + public readonly getItem = (): DetailedListItem => { + return this.props.listItem; + }; + + private readonly showActionMenuOverlay = (listItem?: DetailedListItem): void => { + this.props.onShowActionMenuOverlay?.(); + this.actionMenuOverlay = new Overlay({ + background: true, + closeOnOutsideClick: true, + referenceElement: this.render, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.CENTER, + horizontalDirection: OverlayHorizontalDirection.END_TO_LEFT, + children: [ + { + type: 'div', + classNames: [ 'mynah-detailed-list-item-actions-overlay' ], + children: this.props.listItem.actions?.map((action) => this.getActionButton(action, true, listItem)) + } + ], + }); + }; + + private getActionButton (action: ChatItemButton, showText?: boolean, listItem?: DetailedListItem): ExtendedHTMLElement { + return DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-detailed-list-item-actions-item' ], + children: [ + new Button({ + testId: testIds.detailedList.action, + icon: action.icon ? new Icon({ icon: action.icon }).render : undefined, + ...(showText === true ? { label: action.text } : {}), + tooltip: action.description, + disabled: action.disabled, + primary: false, + border: false, + confirmation: action.confirmation, + status: action.status, + onClick: (e) => { + cancelEvent(e); + this.props.onActionClick?.(action, listItem); + this.hideActionMenuOverlay(); + }, + }).render + ] + }); + } + + private readonly hideActionMenuOverlay = (): void => { + if (this.actionMenuOverlay !== undefined) { + this.actionMenuOverlay.close(); + this.actionMenuOverlay = undefined; + } + }; +} diff --git a/vendor/mynah-ui/src/components/detailed-list/detailed-list-sheet.ts b/vendor/mynah-ui/src/components/detailed-list/detailed-list-sheet.ts new file mode 100644 index 00000000..550bb5a7 --- /dev/null +++ b/vendor/mynah-ui/src/components/detailed-list/detailed-list-sheet.ts @@ -0,0 +1,85 @@ +import { MynahUIGlobalEvents } from '../../helper/events'; +import { ChatItemButton, DetailedList, DetailedListItem, MynahEventNames } from '../../static'; +import { SheetProps } from '../sheet'; +import { DetailedListWrapper } from './detailed-list'; + +export interface DetailedListSheetProps { + tabId?: string; // TODO: remove this in new major version, still here for backwards compatibility + detailedList: DetailedList; + events?: { + onFilterValueChange?: (filterValues: Record, isValid: boolean) => void; + onKeyPress?: (e: KeyboardEvent) => void; + onItemSelect?: (detailedListItem: DetailedListItem) => void; + onItemClick?: (detailedListItem: DetailedListItem) => void; + onBackClick?: () => void; + onTitleActionClick?: (action: ChatItemButton) => void; + onActionClick?: (action: ChatItemButton, listItem?: DetailedListItem) => void; + onFilterActionClick?: (action: ChatItemButton, filterValues?: Record, isValid?: boolean) => void; + onClose?: () => void; + }; +} + +export class DetailedListSheet { + props: DetailedListSheetProps; + detailedListWrapper: DetailedListWrapper; + private readonly keyPressHandler: (e: KeyboardEvent) => void; + + constructor (props: DetailedListSheetProps) { + this.props = props; + // To prevent the header from being shown in the detailed list wrapper + const detailedListCopy: DetailedList = { ...props.detailedList, header: undefined }; + this.detailedListWrapper = new DetailedListWrapper({ + detailedList: detailedListCopy, + onFilterValueChange: props.events?.onFilterValueChange, + onItemSelect: props.events?.onItemSelect, + onItemClick: props.events?.onItemClick, + onItemActionClick: props.events?.onActionClick, + onFilterActionClick: props.events?.onFilterActionClick, + }); + this.keyPressHandler = (e: KeyboardEvent) => { + this.props.events?.onKeyPress?.(e); + }; + } + + open = (showBackButton?: boolean): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.OPEN_SHEET, { + fullScreen: true, + title: this.props.detailedList.header?.title, + description: this.props.detailedList.header?.description, + status: this.props.detailedList.header?.status, + actions: this.props.detailedList.header?.actions, + children: [ this.detailedListWrapper.render ], + showBackButton, + onClose: () => { + this.props.events?.onClose?.(); + window.removeEventListener('keydown', this.keyPressHandler); + }, + onActionClick: (action: ChatItemButton) => { + this.props.events?.onTitleActionClick?.(action); + }, + onBack: () => { + this.props.events?.onBackClick?.(); + } + } satisfies Partial); + + window.addEventListener('keydown', this.keyPressHandler); + }; + + update = (detailedList: DetailedList, showBackButton?: boolean): void => { + this.props.detailedList = { ...this.props.detailedList, ...detailedList }; + if (detailedList.header != null) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.UPDATE_SHEET, { + title: this.props.detailedList.header?.title, + description: this.props.detailedList.header?.description, + status: this.props.detailedList.header?.status, + showBackButton, + actions: this.props.detailedList.header?.actions + } satisfies Partial); + } + this.detailedListWrapper.update({ ...this.props.detailedList, header: undefined }); + }; + + close = (): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CLOSE_SHEET); + }; +} diff --git a/vendor/mynah-ui/src/components/detailed-list/detailed-list.ts b/vendor/mynah-ui/src/components/detailed-list/detailed-list.ts new file mode 100644 index 00000000..df73fc1d --- /dev/null +++ b/vendor/mynah-ui/src/components/detailed-list/detailed-list.ts @@ -0,0 +1,365 @@ +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import testIds from '../../helper/test-ids'; +import { ChatItemButton, DetailedList, DetailedListItem, DetailedListItemGroup } from '../../static'; +import { CardBody } from '../card/card-body'; +import { Icon } from '../icon'; +import { ChatItemButtonsWrapper } from '../chat-item/chat-item-buttons'; +import { DetailedListItemWrapper } from './detailed-list-item'; +import { chunkArray } from '../../helper/quick-pick-data-handler'; +import { ChatItemFormItemsWrapper } from '../chat-item/chat-item-form-items'; +import { TitleDescriptionWithIcon } from '../title-description-with-icon'; +import { generateUID } from '../../main'; +import { Card } from '../card/card'; +import { cancelEvent } from '../../helper/events'; + +export interface DetailedListWrapperProps { + detailedList: DetailedList; + descriptionTextDirection?: 'ltr' | 'rtl'; + onFilterValueChange?: (filterValues: Record, isValid: boolean) => void; + onGroupActionClick?: (action: ChatItemButton, groupName?: string) => void; + onGroupClick?: (groupName: string) => void; + onItemSelect?: (detailedListItem: DetailedListItem) => void; + onItemClick?: (detailedListItem: DetailedListItem) => void; + onItemActionClick?: (action: ChatItemButton, detailedListItem?: DetailedListItem) => void; + onFilterActionClick?: (action: ChatItemButton, filterValues?: Record, isValid?: boolean) => void; +} + +export class DetailedListWrapper { + render: ExtendedHTMLElement; + private readonly detailedListItemGroupsContainer: ExtendedHTMLElement; + private filterForm: ChatItemFormItemsWrapper; + private readonly filtersContainer: ExtendedHTMLElement; + private readonly filterActionsContainer: ExtendedHTMLElement; + private readonly headerContainer: ExtendedHTMLElement; + private readonly props: DetailedListWrapperProps; + private detailedListItemsBlockData: Array<{ + data: DetailedListItem[]; + element: ExtendedHTMLElement; + }> = []; + + private activeTargetElementIndex: number = -1; + private allSelectableDetailedListElements: DetailedListItemWrapper[] = []; + constructor (props: DetailedListWrapperProps) { + this.props = props; + this.headerContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-detailed-list-header-wrapper' ], + children: this.getHeader() + }); + this.filtersContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-detailed-list-filters-wrapper' ], + children: this.getFilters() + }); + this.filterActionsContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-detailed-list-filter-actions-wrapper' ], + children: this.getFilterActions() + }); + this.detailedListItemGroupsContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-detailed-list-item-groups-wrapper' ], + children: this.getDetailedListItemGroups(), + events: { + scroll: this.handleScroll + } + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.quickPicksWrapper, + classNames: [ 'mynah-detailed-list' ], + children: [ + this.headerContainer, + this.filtersContainer, + this.detailedListItemGroupsContainer, + this.filterActionsContainer + ] + }); + } + + /** + * Handles scroll events to implement virtualization for the detailed list: + * + * 1. Initially creating empty placeholder blocks with appropriate height for each chunk of list items + * 2. On scroll, determining which blocks are visible in the viewport (or near it) + * 3. Dynamically rendering content only for visible blocks by: + * - Adding DOM elements for blocks entering the viewport + * - Removing DOM elements for blocks that are no longer visible + * + */ + private readonly handleScroll = (): void => { + const wrapperOffsetHeight = this.detailedListItemGroupsContainer.offsetHeight; + const wrapperScrollTop = this.detailedListItemGroupsContainer.scrollTop; + const buffer = wrapperOffsetHeight; + + this.detailedListItemsBlockData.forEach(itemsBlock => { + const itemBlockTop = itemsBlock.element.offsetTop; + const itemBlockBottom = itemBlockTop + itemsBlock.element.offsetHeight; + const hasChildren = itemsBlock.element.childNodes.length > 0; + + const isVisible = (itemBlockTop < wrapperScrollTop + wrapperOffsetHeight + buffer) && + (itemBlockBottom > wrapperScrollTop - buffer); + + if (!hasChildren && isVisible) { + // Block is visible but not rendered yet - add DOM elements + itemsBlock.element.update({ + children: this.getDetailedListItemElements(itemsBlock.data) + }); + } else if (hasChildren && !isVisible) { + // Block has children but is no longer visible - remove DOM elements + itemsBlock.element.clear(); + } + }); + }; + + private readonly getHeader = (): Array => { + if (this.props.detailedList.header != null) { + return [ new TitleDescriptionWithIcon({ + description: DomBuilder.getInstance().build({ + type: 'div', + children: [ + this.props.detailedList.header.description ?? '', + ...(this.props.detailedList.header.status != null + ? [ new Card({ + testId: testIds.sheet.description, + border: true, + padding: 'medium', + status: this.props.detailedList.header.status?.status, + children: [ new TitleDescriptionWithIcon({ + description: this.props.detailedList.header.status?.description, + title: this.props.detailedList.header.status?.title, + icon: this.props.detailedList.header.status?.icon + }).render ], + }).render ] + : []) + ] + }), + icon: this.props.detailedList.header.icon, + title: this.props.detailedList.header.title, + }).render ]; + } + return [ '' ]; + }; + + private readonly getFilters = (): Array => { + if (this.props.detailedList.filterOptions != null && this.props.detailedList.filterOptions.length > 0) { + this.filterForm = new ChatItemFormItemsWrapper({ + tabId: '', + chatItem: { + formItems: this.props.detailedList.filterOptions + }, + onFormChange: this.props.onFilterValueChange + }); + return [ this.filterForm.render ]; + } + return [ '' ]; + }; + + private readonly getFilterActions = (): ExtendedHTMLElement[] => { + return [ new ChatItemButtonsWrapper({ + onActionClick: (action) => { + this.props.onFilterActionClick?.(action, this.filterForm?.getAllValues(), this.filterForm?.isFormValid()); + }, + buttons: this.props.detailedList.filterActions ?? [], + }).render ]; + }; + + private readonly getDetailedListItemGroups = (): Array => { + const groups = this.props.detailedList.list?.map((detailedListGroup: DetailedListItemGroup) => { + return DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.quickPicksGroup, + classNames: [ 'mynah-detailed-list-group' ], + children: [ + ...(detailedListGroup.groupName !== undefined + ? [ DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.prompt.quickPicksGroupTitle, + classNames: [ 'mynah-detailed-list-group-title', (this.props.onGroupClick != null && this.props.detailedList.selectable === 'clickable') ? 'mynah-group-title-clickable' : '' ], + children: [ + ...(detailedListGroup.icon != null ? [ new Icon({ icon: detailedListGroup.icon }).render ] : []), + new CardBody({ + body: detailedListGroup.groupName, + }).render, + new ChatItemButtonsWrapper({ + buttons: (detailedListGroup.actions ?? []).map(action => ({ + id: action.id, + status: action.status, + icon: action.icon, + text: action.text, + disabled: false + })), + onActionClick: (action) => { this.props.onGroupActionClick?.(action, detailedListGroup.groupName); } + }).render + ], + events: { + click: (e) => { + if (this.props.onGroupClick != null && detailedListGroup.groupName != null && this.props.detailedList.selectable === 'clickable') { + cancelEvent(e); + this.props.onGroupClick(detailedListGroup.groupName); + } + } + } + }) ] + : []), + ...((chunkArray(detailedListGroup.children ?? [], 100)).map((detailedListItemPart, index) => { + const itemBlockKey = generateUID(); + const detailedListItemBlock = DomBuilder.getInstance().build({ + type: 'div', + attributes: { + key: itemBlockKey, + style: `min-height: calc(${detailedListItemPart.length} * (var(--mynah-sizing-8) + var(--mynah-sizing-half)));` + }, + classNames: [ 'mynah-detailed-list-items-block', (detailedListGroup.groupName !== undefined && detailedListGroup.childrenIndented === true) ? 'indented' : '' ], + children: index < 5 + ? this.getDetailedListItemElements(detailedListItemPart) + : [] + }); + this.detailedListItemsBlockData.push({ + data: detailedListItemPart, + element: detailedListItemBlock + }); + return detailedListItemBlock; + })) + ] + }); + }); + return groups ?? [ '' ]; + }; + + private readonly getDetailedListItemElements = (detailedListItems: DetailedListItem[]): ExtendedHTMLElement[] => { + return detailedListItems.map(detailedListItem => { + const detailedListItemElement = new DetailedListItemWrapper({ + listItem: detailedListItem, + onSelect: this.props.onItemSelect, + onClick: this.props.onItemClick, + onShowActionMenuOverlay: () => { this.setFocus(detailedListItem); }, + onActionClick: this.props.onItemActionClick, + selectable: this.props.detailedList.selectable !== false && this.props.detailedList.selectable !== 'clickable', + clickable: this.props.detailedList.selectable === 'clickable', + textDirection: this.props.detailedList.textDirection, + descriptionTextDirection: this.props.descriptionTextDirection + }); + if (detailedListItem.disabled !== true) { + this.allSelectableDetailedListElements.push(detailedListItemElement); + } + return detailedListItemElement.render; + }); + }; + + public readonly changeTarget = ( + direction: 'up' | 'down', + snapOnLastAndFirst?: boolean, + scrollIntoView?: boolean + ): void => { + if (this.allSelectableDetailedListElements.length === 0) return; + + const lastIndex = this.allSelectableDetailedListElements.length - 1; + let nextElementIndex = this.activeTargetElementIndex; + + // Handle initial selection when no item is selected + if (nextElementIndex === -1) { + nextElementIndex = direction === 'up' ? lastIndex : 0; + this.setFocusByIndex(nextElementIndex, scrollIntoView); + return; + } + + // Calculate next index based on direction + if (direction === 'up') { + nextElementIndex = nextElementIndex > 0 + ? nextElementIndex - 1 + : (snapOnLastAndFirst === true ? 0 : lastIndex); + } else { + nextElementIndex = nextElementIndex < lastIndex + ? nextElementIndex + 1 + : (snapOnLastAndFirst === true ? lastIndex : 0); + } + + this.setFocusByIndex(nextElementIndex, scrollIntoView); + }; + + private readonly setFocus = (detailedListItem: DetailedListItem): void => { + // Only remove focus from current item if one is selected + if (this.activeTargetElementIndex >= 0) { + this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(false, false); + } + const selectedItemIndex = this.allSelectableDetailedListElements.findIndex(item => item.getItem().id === detailedListItem.id); + + this.activeTargetElementIndex = selectedItemIndex; + this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(true, false); + }; + + private readonly setFocusByIndex = (index: number, scrollIntoView?: boolean): void => { + // Only remove focus from current item if one is selected + if (this.activeTargetElementIndex >= 0) { + this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(false, scrollIntoView === true); + } + + this.activeTargetElementIndex = index; + this.allSelectableDetailedListElements[this.activeTargetElementIndex].setFocus(true, scrollIntoView === true); + }; + + public readonly getTargetElement = (): DetailedListItem | null => { + if (this.allSelectableDetailedListElements.length === 0 || this.activeTargetElementIndex < 0) { + return null; + } + + return this.allSelectableDetailedListElements[this.activeTargetElementIndex].getItem(); + }; + + public readonly update = (detailedList: DetailedList, preserveScrollPosition?: boolean): void => { + if (detailedList.header != null) { + this.props.detailedList.header = detailedList.header; + this.headerContainer.update({ + children: this.getHeader() + }); + } + + if (detailedList.filterOptions != null) { + this.props.detailedList.filterOptions = detailedList.filterOptions; + this.filtersContainer.update({ + children: this.getFilters() + }); + } + + if (detailedList.filterActions != null) { + this.props.detailedList.filterActions = detailedList.filterActions; + this.filterActionsContainer.update({ + children: this.getFilterActions() + }); + } + + if (detailedList.list != null) { + // Save current scroll position if preserveScrollPosition is true + const scrollTop = preserveScrollPosition === true ? this.detailedListItemGroupsContainer.scrollTop : 0; + if (detailedList.selectable != null) { + this.props.detailedList.selectable = detailedList.selectable; + } + + // Clear and recreate the list structure + this.detailedListItemsBlockData = []; + this.detailedListItemGroupsContainer.clear(); + this.activeTargetElementIndex = -1; + this.allSelectableDetailedListElements = []; + this.props.detailedList.list = detailedList.list; + + // Update with new content + this.detailedListItemGroupsContainer.update({ + children: this.getDetailedListItemGroups() + }); + + // Restore scroll position after DOM update if preserveScrollPosition is true + if (preserveScrollPosition === true) { + // Use requestAnimationFrame to ensure the DOM has been updated + requestAnimationFrame(() => { + // Set the scroll position + this.detailedListItemGroupsContainer.scrollTop = scrollTop; + + // Trigger the virtualization logic using the existing handler + this.handleScroll(); + }); + } + } + }; +} diff --git a/vendor/mynah-ui/src/components/dropdown-form/base-dropdown.ts b/vendor/mynah-ui/src/components/dropdown-form/base-dropdown.ts new file mode 100644 index 00000000..bfac43d8 --- /dev/null +++ b/vendor/mynah-ui/src/components/dropdown-form/base-dropdown.ts @@ -0,0 +1,387 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { Button } from '../button'; +import { Icon, MynahIcons } from '../icon'; +import { generateUID } from '../../helper/guid'; +import { MynahUIGlobalEvents } from '../../helper/events'; +import { MynahEventNames, MynahPortalNames } from '../../static'; +import testIds from '../../helper/test-ids'; + +export interface BaseDropdownProps { + description?: string; + descriptionLink?: { + id: string; + text: string; + destination: string; + onClick?: () => void; + }; + items: T[]; + onChange?: (selectedItems: T[]) => void; + tabId?: string; + messageId?: string; + classNames?: string[]; +} + +export abstract class BaseDropdown { + render: ExtendedHTMLElement; + protected readonly props: BaseDropdownProps; + protected readonly tabId: string; + protected readonly messageId: string; + protected dropdownContent: ExtendedHTMLElement | null = null; + protected dropdownPortal: ExtendedHTMLElement | null = null; + protected readonly uid: string; + protected isOpen = false; + protected selectedItems: T[] = []; + protected dropdownIcon: ExtendedHTMLElement; + protected readonly sheetOpenListenerId: string | null = null; + + // Abstract methods that subclasses must implement + protected abstract createItemElement (item: T): ExtendedHTMLElement; + protected abstract handleItemSelection (item: T): void; + protected abstract getItemSelectionState (item: T): boolean; + protected abstract getDisplayLabel (): string; + + // Helper method to get CSS variable values with calc() support + protected getCSSVariableValue (variableName: string, fallback: number): number { + const value = getComputedStyle(document.documentElement).getPropertyValue(variableName).trim(); + + if (value.length > 0) { + let numericValue: number; + + if (value.includes('calc(')) { + // For calc expressions, create a temporary element to get the computed value + const tempDiv = document.createElement('div'); + tempDiv.style.position = 'absolute'; + tempDiv.style.visibility = 'hidden'; + tempDiv.style.width = value; + document.body.appendChild(tempDiv); + const computedWidth = getComputedStyle(tempDiv).width; + document.body.removeChild(tempDiv); + numericValue = parseFloat(computedWidth.replace(/px$/, '')); + } else { + // Remove 'px' suffix if present and parse the numeric value + const cleanValue = value.replace(/px$/, ''); + numericValue = parseFloat(cleanValue); + } + + return isNaN(numericValue) ? fallback : numericValue; + } + + return fallback; + } + + constructor (props: BaseDropdownProps) { + this.props = props; + this.uid = generateUID(); + + // Initialize messageId + tabId + this.tabId = props.tabId ?? ''; + this.messageId = props.messageId ?? ''; + + // Initialize selected items + this.selectedItems = this.getInitialSelection(); + + // Initialize the dropdown icon + this.dropdownIcon = new Icon({ icon: MynahIcons.DOWN_OPEN }).render; + + // Create the main dropdown button with the selected item's label if available + const initialLabel = this.getDisplayLabel(); + const dropdownButton = new Button({ + label: initialLabel, + icon: this.dropdownIcon, + onClick: this.toggleDropdown, + primary: false, + status: 'dimmed-clear', + classNames: [ 'mynah-dropdown-list-button' ], + testId: testIds.dropdownList.button + }).render; + + // Create the main container (without dropdown content) + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.dropdownList.wrapper, + classNames: [ + 'mynah-dropdown-list-wrapper', + ...(props.classNames ?? []) + ], + attributes: { + id: this.uid + }, + children: [ + dropdownButton + ] + }); + + // Add click outside listener to close dropdown (use capture phase to catch events before stopPropagation) + document.addEventListener('click', this.handleClickOutside, true); + } + + protected getInitialSelection (): T[] { + // Default implementation - subclasses can override if needed + return []; + } + + protected readonly updateUI = (): void => { + // Update dropdown items (if dropdown is open) + if (this.dropdownContent != null) { + const itemElements = this.dropdownContent.querySelectorAll('[data-item-id]'); + + Array.from(itemElements).forEach((element) => { + const itemElement = element as ExtendedHTMLElement; + const itemId = itemElement.getAttribute('data-item-id'); + if (itemId == null) return; + + const item = this.props.items.find(item => this.getItemId(item) === itemId); + if (item == null) return; + + // Replace the entire element with updated version + const updatedElement = this.createItemElement(item); + itemElement.replaceWith(updatedElement); + }); + } + + // Update button label + const buttonLabel = this.render.querySelector('.mynah-dropdown-list-button .mynah-button-label'); + if (buttonLabel != null) { + buttonLabel.innerHTML = this.getDisplayLabel(); + } + }; + + protected abstract getItemId (item: T): string; + + protected readonly onLinkClick = (buttonId: string): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.DROPDOWN_LINK_CLICK, { + tabId: this.props.tabId, + actionId: buttonId, + destination: this.props.descriptionLink?.destination + }); + }; + + protected readonly toggleDropdown = (e: Event): void => { + e.stopPropagation(); + this.isOpen = !this.isOpen; + this.isOpen ? this.openDropdown() : this.closeDropdown(); + }; + + protected readonly openDropdown = (): void => { + // Create the dropdown content + this.dropdownContent = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-dropdown-list-content', 'open' ], + children: [ + { + type: 'div', + classNames: [ 'mynah-dropdown-list-options' ], + children: this.props.items.map(item => this.createItemElement(item)) + }, + { + type: 'div', + classNames: [ 'mynah-dropdown-list-footer' ], + children: [ + ...((this.props.description != null && this.props.description.trim() !== '') || this.props.descriptionLink != null + ? [ { + type: 'p', + testId: testIds.dropdownList.description, + classNames: [ 'mynah-dropdown-list-description' ], + children: [ + ...(this.props.description != null && this.props.description.trim() !== '' + ? [ this.props.description ] + : []), + ...(this.props.descriptionLink != null + ? (() => { + const descriptionLink = this.props.descriptionLink; + return [ { + type: 'button', + classNames: [ 'mynah-dropdown-list-description-link' ], + events: { + click: (e: Event) => { + e.stopPropagation(); + this.onLinkClick(descriptionLink.id); + } + }, + children: [ descriptionLink.text ] + } ]; + })() + : []) + ] + } ] + : []) + ] + }, + ] + }); + + // Create portal container + this.dropdownPortal = DomBuilder.getInstance().createPortal( + `${MynahPortalNames.SHEET}-dropdown-${this.uid}`, + { + type: 'div', + testId: testIds.dropdownList.portal, + classNames: [ 'mynah-dropdown-list-portal' ], + events: { + click: (event: MouseEvent) => { + // Prevent closing when clicking inside the dropdown + event.stopPropagation(); + } + }, + children: [ this.dropdownContent ] + }, + 'beforeend' + ); + + // Position the dropdown and add scroll listeners + this.updateDropdownPosition(); + window.addEventListener('scroll', this.updateDropdownPosition, true); + window.addEventListener('resize', this.updateDropdownPosition); + + // Update the icon to UP_OPEN when the dropdown is open + this.dropdownIcon.replaceWith(new Icon({ icon: MynahIcons.UP_OPEN }).render); + this.dropdownIcon = this.render.querySelector('.mynah-dropdown-list-button .mynah-ui-icon') as ExtendedHTMLElement; + }; + + protected readonly isElementVisible = (element: Element): boolean => { + const rect = element.getBoundingClientRect(); + + // Check viewport bounds first (quick check) + const viewportHeight = window.innerHeight ?? document.documentElement.clientHeight; + const viewportWidth = window.innerWidth ?? document.documentElement.clientWidth; + if (rect.bottom < 0 || rect.top > viewportHeight || rect.right < 0 || rect.left > viewportWidth) { + return false; + } + + // Check parent containers with overflow + for (let parent = element.parentElement; parent != null; parent = parent.parentElement) { + const parentStyle = window.getComputedStyle(parent); + const hasOverflow = [ 'overflow', 'overflowX', 'overflowY' ].some( + prop => parentStyle[prop as any] !== 'visible' + ); + + if (hasOverflow) { + const parentRect = parent.getBoundingClientRect(); + if (rect.bottom < parentRect.top || rect.top > parentRect.bottom || + rect.right < parentRect.left || rect.left > parentRect.right) { + return false; + } + } + } + + return true; + }; + + protected readonly updateDropdownPosition = (): void => { + if (this.dropdownPortal == null) return; + + // Close dropdown if button is not visible + if (!this.isElementVisible(this.render)) { + this.isOpen = false; + this.closeDropdown(); + return; + } + + // Calculate position using CSS variables + const buttonRect = this.render.getBoundingClientRect(); + const dropdownWidth = this.getCSSVariableValue('--mynah-dropdown-width', 250); + const dropdownMargin = this.getCSSVariableValue('--mynah-dropdown-margin', 4); + + // Position dropdown below button with margin + const calculatedTop = buttonRect.bottom + dropdownMargin; + + // Align with chat item card if present, otherwise align with button + const chatItemCard = this.render.closest('.mynah-chat-item-card'); + const calculatedLeft = (chatItemCard != null) + ? chatItemCard.getBoundingClientRect().right - dropdownWidth + : buttonRect.right - dropdownWidth; + + // Update position + this.dropdownPortal.style.top = `${calculatedTop}px`; + this.dropdownPortal.style.left = `${calculatedLeft}px`; + }; + + protected readonly closeDropdown = (): void => { + // Remove scroll and resize listeners + window.removeEventListener('scroll', this.updateDropdownPosition, true); + window.removeEventListener('resize', this.updateDropdownPosition); + + // Remove the portal + if (this.dropdownPortal != null) { + this.dropdownPortal.remove(); + this.dropdownPortal = null; + } + this.dropdownContent = null; + + // Update the icon to DOWN_OPEN when the dropdown is closed + this.dropdownIcon.replaceWith(new Icon({ icon: MynahIcons.DOWN_OPEN }).render); + this.dropdownIcon = this.render.querySelector('.mynah-dropdown-list-button .mynah-ui-icon') as ExtendedHTMLElement; + }; + + protected readonly handleClickOutside = (e: MouseEvent): void => { + if (!this.isOpen) return; + + const target = e.target as Node; + + // Don't close if clicking inside the dropdown portal + if (this.dropdownPortal?.contains(target) ?? false) { + return; + } + + // Don't close if clicking on this dropdown's button + if (this.render.contains(target)) { + return; + } + + // Close the dropdown for any other click + this.isOpen = false; + this.closeDropdown(); + this.dispatchChangeEvent(); + }; + + public readonly getSelectedItems = (): T[] => { + return [ ...this.selectedItems ]; + }; + + public readonly setSelectedItems = (itemIds: string[]): void => { + this.selectedItems = this.props.items.filter(item => + itemIds.includes(this.getItemId(item)) + ); + this.updateUI(); + }; + + protected readonly dispatchChangeEvent = (): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.DROPDOWN_OPTION_CHANGE, { + value: this.selectedItems, + messageId: this.messageId, + tabId: this.tabId + }); + + // Also trigger onChange callback if provided + if (this.props.onChange != null) { + this.props.onChange(this.selectedItems); + } + }; + + public readonly destroy = (): void => { + document.removeEventListener('click', this.handleClickOutside, true); + + // Remove sheet open listener if it exists + if (this.sheetOpenListenerId != null) { + MynahUIGlobalEvents.getInstance().removeListener(MynahEventNames.OPEN_SHEET, this.sheetOpenListenerId); + } + + // Remove scroll and resize listeners if dropdown is open + if (this.isOpen) { + window.removeEventListener('scroll', this.updateDropdownPosition, true); + window.removeEventListener('resize', this.updateDropdownPosition); + } + + // Clean up portal if it exists + if (this.dropdownPortal != null) { + this.dropdownPortal.remove(); + this.dropdownPortal = null; + } + this.dropdownContent = null; + }; +} diff --git a/vendor/mynah-ui/src/components/dropdown-form/dropdown-list.ts b/vendor/mynah-ui/src/components/dropdown-form/dropdown-list.ts new file mode 100644 index 00000000..4ff96f17 --- /dev/null +++ b/vendor/mynah-ui/src/components/dropdown-form/dropdown-list.ts @@ -0,0 +1,101 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { StyleLoader } from '../../helper/style-loader'; +import { Icon, MynahIcons } from '../icon'; +import { DropdownListOption, DropdownListProps } from '../../static'; +import testIds from '../../helper/test-ids'; +import { BaseDropdown, BaseDropdownProps } from './base-dropdown'; + +export class DropdownList extends BaseDropdown { + constructor (props: DropdownListProps) { + // Handle backward compatibility - support both 'options' and 'items' + const normalizedProps: BaseDropdownProps = { + ...props, + items: props.options ?? [] // Map 'options' to 'items' for base class + }; + + super(normalizedProps); + + // Load the specific CSS for dropdown list + StyleLoader.getInstance().load('components/_dropdown-list.scss'); + } + + protected getInitialSelection (): DropdownListOption[] { + // Initialize selected items from options that have selected: true + return this.props.items.filter(option => option.selected ?? false); + } + + protected createItemElement (option: DropdownListOption): ExtendedHTMLElement { + const isSelected = this.getItemSelectionState(option); + + return DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.dropdownList.option, + classNames: [ + 'mynah-dropdown-list-option', + ...(isSelected ? [ 'selected' ] : []) + ], + attributes: { + 'data-item-id': option.id + }, + events: { + click: (e) => { + e.stopPropagation(); + this.handleItemSelection(option); + } + }, + children: [ + { + type: 'div', + classNames: [ 'mynah-dropdown-list-checkbox' ], + children: [ + ...(isSelected ? [ new Icon({ icon: MynahIcons.OK, classNames: [ 'mynah-dropdown-list-check-icon' ] }).render ] : []) + ] + }, + { + type: 'span', + testId: testIds.dropdownList.optionLabel, + classNames: [ 'mynah-dropdown-list-option-label' ], + children: [ + option.label + ] + } + ] + }); + } + + protected handleItemSelection (option: DropdownListOption): void { + // Select only this option (single selection behavior) + this.selectedItems = [ option ]; + + // Update UI, close dropdown and dispatch event + this.updateUI(); + this.isOpen = false; + this.closeDropdown(); + this.dispatchChangeEvent(); + } + + protected getItemSelectionState (option: DropdownListOption): boolean { + return this.selectedItems.some(selectedOption => selectedOption.id === option.id); + } + + protected getDisplayLabel (): string { + return this.selectedItems.length > 0 ? this.selectedItems[0].label : ''; + } + + protected getItemId (option: DropdownListOption): string { + return option.id; + } + + public readonly getSelectedOptions = (): DropdownListOption[] => { + return this.getSelectedItems(); + }; + + public readonly setSelectedOptions = (optionIds: string[]): void => { + this.setSelectedItems(optionIds); + }; +} diff --git a/vendor/mynah-ui/src/components/dropdown-form/dropdown-wrapper.ts b/vendor/mynah-ui/src/components/dropdown-form/dropdown-wrapper.ts new file mode 100644 index 00000000..cd4ffe80 --- /dev/null +++ b/vendor/mynah-ui/src/components/dropdown-form/dropdown-wrapper.ts @@ -0,0 +1,71 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { DropdownFactoryProps, DropdownListOption } from '../../static'; +import { DropdownList } from './dropdown-list'; +import testIds from '../../helper/test-ids'; + +export interface DropdownWrapperProps { + dropdownProps: DropdownFactoryProps; + classNames?: string[]; +} + +export class DropdownWrapper { + private readonly props: DropdownWrapperProps; + private dropdown: DropdownList | null = null; + + render: ExtendedHTMLElement; + + constructor (props: DropdownWrapperProps) { + this.props = props; + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.dropdownList.wrapper, + classNames: [ + 'mynah-dropdown-wrapper', + ...(this.props.classNames ?? []) + ], + children: [ this.createDropdownComponent() ] + }); + } + + private readonly createDropdownComponent = (): ExtendedHTMLElement => { + const { dropdownProps } = this.props; + + // For now, all types use DropdownList + // Future implementations can add specific components for radio, checkbox, etc. + switch (dropdownProps.type) { + case 'select': + case 'radio': + case 'checkbox': + default: + this.dropdown = new DropdownList({ + description: dropdownProps.description, + descriptionLink: dropdownProps.descriptionLink, + options: dropdownProps.options, + onChange: dropdownProps.onChange, + tabId: dropdownProps.tabId, + messageId: dropdownProps.messageId, + classNames: dropdownProps.classNames + }); + return this.dropdown.render; + } + }; + + public readonly getSelectedItems = (): DropdownListOption[] => { + return this.dropdown?.getSelectedItems() ?? []; + }; + + public readonly setSelectedItems = (itemIds: string[]): void => { + this.dropdown?.setSelectedItems(itemIds); + }; + + public readonly destroy = (): void => { + this.dropdown?.destroy(); + this.dropdown = null; + }; +} diff --git a/vendor/mynah-ui/src/components/feedback-form/feedback-form-comment.ts b/vendor/mynah-ui/src/components/feedback-form/feedback-form-comment.ts new file mode 100644 index 00000000..a3314762 --- /dev/null +++ b/vendor/mynah-ui/src/components/feedback-form/feedback-form-comment.ts @@ -0,0 +1,36 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import testIds from '../../helper/test-ids'; + +export interface FeedbackFormCommentProps { + onChange?: (comment: string) => void; + initComment?: string; +} +export class FeedbackFormComment { + render: ExtendedHTMLElement; + + constructor (props: FeedbackFormCommentProps) { + this.render = DomBuilder.getInstance().build({ + type: 'textarea', + testId: testIds.feedbackForm.comment, + events: { + keyup: (e: InputEvent) => { + if (props.onChange !== undefined) { + props.onChange(this.render.value); + } + }, + }, + classNames: [ 'mynah-feedback-form-comment' ], + }); + + // Set the initial value after creating the element + this.render.value = props.initComment ?? ''; + } + + getComment = (): string => this.render.value; + clear = (): void => { this.render.value = ''; }; +} diff --git a/vendor/mynah-ui/src/components/feedback-form/feedback-form.ts b/vendor/mynah-ui/src/components/feedback-form/feedback-form.ts new file mode 100644 index 00000000..71b269a8 --- /dev/null +++ b/vendor/mynah-ui/src/components/feedback-form/feedback-form.ts @@ -0,0 +1,239 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChatItem, ChatItemButton, ChatItemFormItem, FeedbackPayload, MynahEventNames } from '../../static'; +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { Button } from '../button'; +import { FeedbackFormComment } from './feedback-form-comment'; +import { cancelEvent, MynahUIGlobalEvents } from '../../helper/events'; +import { Config } from '../../helper/config'; +import { Select } from '../form-items/select'; +import testIds from '../../helper/test-ids'; +import { MynahUITabsStore } from '../../helper/tabs-store'; +import { ChatItemFormItemsWrapper } from '../chat-item/chat-item-form-items'; +import { ChatItemButtonsWrapper } from '../chat-item/chat-item-buttons'; + +export interface FeedbackFormProps { + initPayload?: FeedbackPayload; +} +export class FeedbackForm { + private readonly feedbackOptionsWrapper: Select; + private readonly feedbackComment: FeedbackFormComment; + private readonly feedbackSubmitButton: Button; + public readonly defaultFeedbackFormItems: ExtendedHTMLElement[]; + private feedbackPayload: FeedbackPayload = { messageId: '', selectedOption: '', tabId: '', comment: '' }; + private chatFormItems: ChatItemFormItemsWrapper | null = null; + private chatButtons: ChatItemButtonsWrapper | null = null; + + constructor (props?: FeedbackFormProps) { + this.feedbackPayload = { + selectedOption: Config.getInstance().config.feedbackOptions[0].value, + messageId: '', + tabId: '', + comment: '', + ...props?.initPayload + }; + + this.feedbackOptionsWrapper = new Select({ + wrapperTestId: testIds.feedbackForm.optionsSelectWrapper, + optionTestId: testIds.feedbackForm.optionsSelect, + options: Config.getInstance().config.feedbackOptions, + onChange: (val) => { + this.feedbackPayload.selectedOption = val; + }, + label: Config.getInstance().config.texts.feedbackFormOptionsLabel, + }); + + this.feedbackComment = new FeedbackFormComment({ + onChange: (comment: string) => { + this.feedbackPayload.comment = comment; + }, + initComment: this.feedbackPayload?.comment, + }); + + this.feedbackSubmitButton = new Button({ + testId: testIds.feedbackForm.submitButton, + label: Config.getInstance().config.texts.submit, + primary: true, + onClick: () => { + this.onFeedbackSet(this.feedbackPayload); + this.close(); + }, + }); + + this.defaultFeedbackFormItems = [ + this.feedbackOptionsWrapper.render, + DomBuilder.getInstance().build({ + type: 'span', + children: [ Config.getInstance().config.texts.feedbackFormCommentLabel ], + }), + this.feedbackComment.render, + DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-feedback-form-buttons-container' ], + children: [ + new Button({ + testId: testIds.feedbackForm.cancelButton, + primary: false, + label: Config.getInstance().config.texts.cancel, + onClick: () => { + this.close(); + } + }).render, + this.feedbackSubmitButton.render + ] + }), + ]; + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.SHOW_FEEDBACK_FORM, (data: { + messageId?: string; tabId: string; customFormData?: { + title?: string; + description?: string; + buttons?: ChatItemButton[]; + formItems?: ChatItemFormItem[]; + }; + }) => { + const title = data.messageId !== undefined + ? Config.getInstance().config.texts.feedbackFormTitle + : data.customFormData !== undefined + ? data.customFormData.title + : undefined; + + const description = data.messageId !== undefined + ? Config.getInstance().config.texts.feedbackFormDescription + : data.customFormData !== undefined + ? data.customFormData.description + : undefined; + + const defaultOrCustomChatItems = data.messageId !== undefined + ? this.defaultFeedbackFormItems + : data.customFormData !== undefined + ? this.getFormItems({ + tabId: data.tabId, + title: data.customFormData?.title, + description: data.customFormData?.description, + onFormDisabled: () => { + this.close(); + }, + onFormAction: () => { + this.close(); + }, + onCloseButtonClick: (e) => { + cancelEvent(e); + this.close(); + }, + chatItem: data.customFormData + }) + : []; + + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.OPEN_SHEET, { + tabId: data.tabId, + title, + description, + children: defaultOrCustomChatItems + }); + if (data.messageId !== undefined) { + this.feedbackPayload.messageId = data.messageId; + } + this.feedbackPayload.tabId = data.tabId; + }); + } + + private readonly onFeedbackSet = (feedbackData: FeedbackPayload): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FEEDBACK_SET, feedbackData); + }; + + close = (): void => { + this.feedbackComment.clear(); + this.feedbackOptionsWrapper.setValue(Config.getInstance().config.feedbackOptions[0].value); + this.feedbackPayload = { + messageId: '', + selectedOption: Config.getInstance().config.feedbackOptions[0].value, + tabId: '', + comment: '' + }; + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CLOSE_SHEET, {}); + }; + + private readonly getFormItems = (data: { + tabId: string; + chatItem: Partial; + title?: string; + description?: string; + onFormAction?: (actionName: string, formData: Record>>) => void; + onFormDisabled?: () => void; + onCloseButtonClick?: (e: Event) => void; + }): Array => { + if (MynahUITabsStore.getInstance().getTabDataStore(data.tabId) === undefined) { + return []; + } + if (this.chatFormItems !== null) { + this.chatFormItems.render.remove(); + this.chatFormItems = null; + } + if (data.chatItem.formItems !== undefined) { + this.chatFormItems = new ChatItemFormItemsWrapper({ + tabId: data.tabId, + chatItem: data.chatItem, + onModifierEnterPress (formData, tabId) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FORM_MODIFIER_ENTER_PRESS, { formData, tabId }); + }, + onTextualItemKeyPress (event, itemId, formData, tabId, disableAllCallback) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FORM_TEXTUAL_ITEM_KEYPRESS, { + event, + formData, + itemId, + tabId, + callback: (disableAll?: boolean) => { + if (disableAll === true) { + disableAllCallback(); + } + } + }); + }, + onFormChange (formData, isValid, tabId) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.FORM_CHANGE, { formData, isValid, tabId }); + }, + }); + } + + if (this.chatButtons !== null) { + this.chatButtons.render.remove(); + this.chatButtons = null; + } + if (data.chatItem.buttons !== undefined) { + this.chatButtons = new ChatItemButtonsWrapper({ + tabId: data.tabId, + formItems: this.chatFormItems, + buttons: data.chatItem.buttons, + onAllButtonsDisabled: data.onFormDisabled, + onActionClick: (action, e) => { + if (e !== undefined) { + cancelEvent(e); + } + + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CUSTOM_FORM_ACTION_CLICK, { + tabId: data.tabId, + id: action.id, + text: action.text, + ...(this.chatFormItems !== null ? { formItemValues: this.chatFormItems.getAllValues() } : {}) + }); + + if (data.onFormAction !== undefined) { + data.onFormAction(action.id, this.chatFormItems !== null ? this.chatFormItems.getAllValues() : {}); + } + } + }); + } + return [ + ...(this.chatFormItems !== null + ? [ (this.chatFormItems).render ] + : []), + ...(this.chatButtons !== null + ? [ (this.chatButtons).render ] + : []), + ]; + }; +} diff --git a/vendor/mynah-ui/src/components/form-items/checkbox.ts b/vendor/mynah-ui/src/components/form-items/checkbox.ts new file mode 100644 index 00000000..71a00bfa --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/checkbox.ts @@ -0,0 +1,139 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import { StyleLoader } from '../../helper/style-loader'; +import { Icon, MynahIcons, MynahIconsType } from '../icon'; + +export interface CheckboxProps { + classNames?: string[]; + attributes?: Record; + title?: HTMLElement | ExtendedHTMLElement | string; + label?: string; + description?: ExtendedHTMLElement; + value?: 'true' | 'false'; + optional?: boolean; + icon?: MynahIcons | MynahIconsType; + onChange?: (value: 'true' | 'false') => void; + wrapperTestId?: string; + optionTestId?: string; +} + +export abstract class CheckboxAbstract { + render: ExtendedHTMLElement; + setValue = (value: 'true' | 'false'): void => {}; + getValue = (): 'true' | 'false' => 'false'; + setEnabled = (enabled: boolean): void => {}; +} +export class CheckboxInternal extends CheckboxAbstract { + private readonly checkboxWrapper: ExtendedHTMLElement; + private readonly checkboxItem: ExtendedHTMLElement; + render: ExtendedHTMLElement; + constructor (props: CheckboxProps) { + StyleLoader.getInstance().load('components/_form-input.scss'); + super(); + this.checkboxItem = DomBuilder.getInstance().build({ + type: 'input', + classNames: [ 'as-checkbox' ], + attributes: { + type: 'checkbox', + }, + }); + this.checkboxItem.checked = props.value === 'true'; + + this.checkboxWrapper = DomBuilder.getInstance().build({ + type: 'div', + testId: props.wrapperTestId, + classNames: [ 'mynah-form-input', ...(props.classNames ?? []) ], + children: [ + { + type: 'div', + classNames: [ 'mynah-form-input-radio-wrapper' ], + children: [ { + type: 'label', + testId: props.optionTestId, + classNames: [ 'mynah-form-input-radio-label' ], + events: { + click: (e) => { + cancelEvent(e); + const checkState = (!this.checkboxItem.checked).toString() as 'true' | 'false'; + this.setValue(checkState); + props.onChange?.(checkState); + } + }, + children: [ + this.checkboxItem, + { + type: 'span', + classNames: [ 'mynah-form-input-radio-check' ], + children: [ + new Icon({ icon: props.icon ?? MynahIcons.OK }).render + ] + }, + ...(props.label != null + ? [ { + type: 'span', + children: [ props.label ] + } ] + : []) + ] + } ] + } ] + }); + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ ...(props.title != null ? [ props.title ] : []) ] + }, + ...[ props.description !== undefined ? props.description : '' ], + { + type: 'div', + classNames: [ 'mynah-form-input-container', 'mynah-form-input-radio-group', 'no-border' ], + ...(props.attributes !== undefined ? { attributes: props.attributes } : {}), + children: [ + this.checkboxWrapper, + ] + }, + ] + }); + } + + setValue = (value: 'true' | 'false'): void => { + this.checkboxItem.checked = value === 'true'; + }; + + getValue = (): 'true' | 'false' => { + return this.checkboxItem.checked.toString() as 'true' | 'false'; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.checkboxWrapper.removeAttribute('disabled'); + this.checkboxItem.removeAttribute('disabled'); + } else { + this.checkboxWrapper.setAttribute('disabled', 'disabled'); + this.checkboxItem.setAttribute('disabled', 'disabled'); + } + }; +} + +export class Checkbox extends CheckboxAbstract { + render: ExtendedHTMLElement; + + constructor (props: CheckboxProps) { + super(); + return new (Config.getInstance().config.componentClasses.Checkbox ?? CheckboxInternal)(props); + } + + setValue = (value: 'true' | 'false'): void => {}; + getValue = (): 'true' | 'false' => 'false'; + setEnabled = (enabled: boolean): void => {}; +} diff --git a/vendor/mynah-ui/src/components/form-items/form-item-list.ts b/vendor/mynah-ui/src/components/form-items/form-item-list.ts new file mode 100644 index 00000000..58481d00 --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/form-item-list.ts @@ -0,0 +1,265 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import { generateUID } from '../../helper/guid'; +import { StyleLoader } from '../../helper/style-loader'; +import { ListItemEntry, SingularFormItem } from '../../static'; +import { Button } from '../button'; +import { ChatItemFormItemsWrapper } from '../chat-item/chat-item-form-items'; +import { Icon, MynahIcons } from '../icon'; + +export interface FormItemListProps { + items: SingularFormItem[]; + value?: ListItemEntry[]; + classNames?: string[]; + attributes?: Record; + label?: HTMLElement | ExtendedHTMLElement | string; + description?: ExtendedHTMLElement; + wrapperTestId?: string; + onChange?: (values: Array< Record>>>) => void; +} + +export abstract class FormItemListAbstract { + render: ExtendedHTMLElement; + setValue = (value: ListItemEntry[]): void => { }; + getValue = (): Array< Record> => []; + setEnabled = (enabled: boolean): void => { }; +} + +export class FormItemListInternal extends FormItemListAbstract { + private readonly rowWrapper: ExtendedHTMLElement; + private readonly addButton: ExtendedHTMLElement; + private readonly props: FormItemListProps; + private readonly rows: Map = new Map(); + render: ExtendedHTMLElement; + + constructor (props: FormItemListProps) { + StyleLoader.getInstance().load('components/form-items/_form-item-list.scss'); + super(); + this.props = props; + + this.rowWrapper = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-list-rows-wrapper' ], + children: [ + DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-list-row' ], + children: [ + ...this.props.items.filter(item => item.description != null || item.title != null).map(item => DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-list-row-header' ], + children: [ + ...(item.title !== undefined + ? [ { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ + ...(item.title !== undefined ? [ item.title ] : []), + ] + } ] + : []), + ...(item.description !== undefined + ? [ { + type: 'span', + classNames: [ 'mynah-ui-form-item-description' ], + children: [ + ...(item.description !== undefined ? [ item.description ] : []), + ] + } ] + : []), + ] + })), + new Button({ + classNames: [ 'mynah-form-item-list-row-remove-all-button' ], + primary: false, + disabled: true, + onClick: (e) => { + // Maybe remove all? + }, + icon: new Icon({ icon: MynahIcons.CANCEL }).render + }).render + ] + }) + ] + }); + + this.addButton = new Button({ + classNames: [ 'mynah-form-item-list-add-button' ], + primary: false, + label: Config.getInstance().config.texts.add, + onClick: (e) => { + cancelEvent(e); + this.addRow(); + }, + icon: new Icon({ icon: MynahIcons.PLUS }).render + }).render; + + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ + ...(props.label !== undefined ? [ props.label ] : []), + ] + }, + ...[ props.description !== undefined ? props.description : '' ], + { + type: 'div', + classNames: [ 'mynah-form-item-list-wrapper' ], + testId: props.wrapperTestId, + children: [ + this.rowWrapper, + this.addButton + ] + }, + ] + }); + + // Initialize with existing values or add an empty row + if (props.value != null && props.value.length > 0) { + props.value?.forEach(entry => this.addRow(entry)); + } else { + this.addRow(); + } + } + + private addRow (entry?: ListItemEntry): void { + const rowId = generateUID(); + const formItems: SingularFormItem[] = []; + + // Create form items container + const formItemsContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-list-row-items-container' ] + }); + + // Create remove button + const removeButton = new Button({ + classNames: [ 'mynah-form-item-list-row-remove-button' ], + primary: false, + disabled: entry?.persistent, + onClick: (e) => { + cancelEvent(e); + this.removeRow(rowId); + }, + icon: new Icon({ icon: MynahIcons.CANCEL }).render + }).render; + + // Create form items + this.props.items.forEach(item => { + item = { ...item, title: undefined, description: undefined }; + formItems.push({ + ...item, + value: entry?.value[item.id] as any + }); + + const value = entry?.value[item.id]; + if (value != null) { + item.value = value; + } + }); + + // Create form render + const newForm = new ChatItemFormItemsWrapper({ + tabId: '', + chatItem: { + formItems + }, + onFormChange: (formData: Record) => { + // this.formData = formData; + this.props.onChange?.(this.getValue()); + }, + }); + formItemsContainer.appendChild(newForm.render); + + // Create row container and add it to the wrapper + const rowContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-list-row' ], + attributes: { + 'data-row-id': rowId + }, + children: [ + formItemsContainer, + removeButton + ] + }); + this.rowWrapper.appendChild(rowContainer); + + // Store the row reference + this.rows.set(rowId, { rowElm: rowContainer, rowForm: newForm }); + this.props.onChange?.(this.getValue()); + } + + private removeRow (rowId: string): void { + const row = this.rows.get(rowId); + if (row != null) { + row.rowElm.remove(); + this.rows.delete(rowId); + this.props.onChange?.(this.getValue()); + } + } + + setValue = (value: ListItemEntry[]): void => { + // Clear existing rows + this.rows.forEach(row => row.rowElm.remove()); + this.rows.clear(); + + // Add new rows + if (value.length > 0) { + value.forEach(entry => this.addRow(entry)); + } else { + this.addRow(); + } + }; + + getValue = (): Array< Record> => { + const values: Array< Record> = []; + this.rows.forEach(row => values.push(row.rowForm.getAllValues() as Record)); + return values; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.render.removeAttribute('disabled'); + this.rows.forEach(row => { + row.rowForm.enableAll(); + }); + } else { + this.render.setAttribute('disabled', 'disabled'); + this.rows.forEach(row => { + row.rowForm.disableAll(); + }); + } + }; + + isFormValid = (): boolean => { + let isValid = true; + this.rows.forEach(row => { + isValid = isValid && row.rowForm.isFormValid(); + }); + return isValid; + }; +} + +export class FormItemList extends FormItemListAbstract { + render: ExtendedHTMLElement; + + constructor (props: FormItemListProps) { + super(); + return new (Config.getInstance().config.componentClasses.FormItemList ?? FormItemListInternal)(props); + } + + setValue = (value: ListItemEntry[]): void => { }; + getValue = (): Array< Record> => []; + setEnabled = (enabled: boolean): void => { }; +} diff --git a/vendor/mynah-ui/src/components/form-items/form-item-pill-box.ts b/vendor/mynah-ui/src/components/form-items/form-item-pill-box.ts new file mode 100644 index 00000000..d4afe826 --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/form-item-pill-box.ts @@ -0,0 +1,203 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import { StyleLoader } from '../../helper/style-loader'; + +export interface FormItemPillBoxProps { + id: string; + value?: string; + classNames?: string[]; + attributes?: Record; + label?: HTMLElement | ExtendedHTMLElement | string; + description?: ExtendedHTMLElement; + placeholder?: string; + wrapperTestId?: string; + onChange?: (value: string) => void; + disabled?: boolean; +} + +export abstract class FormItemPillBoxAbstract { + render: ExtendedHTMLElement; + setValue = (value: string): void => { }; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => { }; +} + +export class FormItemPillBoxInternal extends FormItemPillBoxAbstract { + private readonly props: FormItemPillBoxProps; + private readonly pillsContainer: ExtendedHTMLElement; + private readonly input: ExtendedHTMLElement; + private readonly wrapper: ExtendedHTMLElement; + private pills: string[] = []; + render: ExtendedHTMLElement; + + constructor (props: FormItemPillBoxProps) { + StyleLoader.getInstance().load('components/form-items/_form-item-pill-box.scss'); + super(); + this.props = props; + + // Create pills container + this.pillsContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-pill-box-pills-container' ], + }); + + // Create input field + this.input = DomBuilder.getInstance().build({ + type: 'textarea', + classNames: [ 'mynah-form-item-pill-box-input' ], + attributes: { + placeholder: props.placeholder ?? 'Type and press Enter to add a tag', + rows: '1', + }, + events: { + keydown: (e: KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + cancelEvent(e); + const value = (this.input as unknown as HTMLTextAreaElement).value.trim(); + if (value !== '') { + this.addPill(value); + (this.input as unknown as HTMLTextAreaElement).value = ''; + this.notifyChange(); + } + } + } + } + }); + + // Create wrapper + this.wrapper = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-pill-box-wrapper' ], + children: [ + this.pillsContainer, + this.input + ] + }); + + // Create main container + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper', ...(props.classNames ?? []) ], + attributes: props.attributes, + testId: props.wrapperTestId, + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ + ...(props.label !== undefined ? [ props.label ] : []), + ] + }, + ...(props.description !== undefined ? [ props.description ] : []), + this.wrapper + ] + }); + + // Initialize with existing value + if (props.value != null) { + this.setValue(props.value); + } + + // Set initial disabled state + if (props.disabled === true) { + this.setEnabled(false); + } + } + + private addPill (text: string): void { + if (text === '' || this.pills.includes(text)) { + return; + } + + this.pills.push(text); + + const pill = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-item-pill' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-item-pill-text' ], + children: [ text ] + }, + { + type: 'span', + classNames: [ 'mynah-form-item-pill-remove' ], + children: [ '×' ], + events: { + click: (e) => { + cancelEvent(e); + pill.remove(); + this.pills = this.pills.filter(p => p !== text); + this.notifyChange(); + } + } + } + ] + }); + + this.pillsContainer.appendChild(pill); + } + + private notifyChange (): void { + if (this.props.onChange != null) { + this.props.onChange(this.getValue()); + } + } + + setValue = (value: string): void => { + // Clear existing pills + this.pillsContainer.innerHTML = ''; + this.pills = []; + + // Add new pills + if (value !== '') { + const pillValues = value.split(/[,\n]+/).map(v => v.trim()).filter(v => v); + pillValues.forEach(pill => this.addPill(pill)); + } + }; + + getValue = (): string => { + return this.pills.join(','); + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.render.removeAttribute('disabled'); + (this.input as unknown as HTMLTextAreaElement).disabled = false; + } else { + this.render.setAttribute('disabled', 'disabled'); + (this.input as unknown as HTMLTextAreaElement).disabled = true; + } + }; +} + +export class FormItemPillBox extends FormItemPillBoxAbstract { + render: ExtendedHTMLElement; + private readonly instance: FormItemPillBoxAbstract; + + constructor (props: FormItemPillBoxProps) { + super(); + const InternalClass = Config.getInstance().config.componentClasses.FormItemPillBox ?? FormItemPillBoxInternal; + this.instance = new InternalClass(props); + this.render = this.instance.render; + } + + setValue = (value: string): void => { + this.instance.setValue(value); + }; + + getValue = (): string => { + return this.instance.getValue(); + }; + + setEnabled = (enabled: boolean): void => { + this.instance.setEnabled(enabled); + }; +} diff --git a/vendor/mynah-ui/src/components/form-items/radio-group.ts b/vendor/mynah-ui/src/components/form-items/radio-group.ts new file mode 100644 index 00000000..15fc7602 --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/radio-group.ts @@ -0,0 +1,158 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import { generateUID } from '../../helper/guid'; +import { StyleLoader } from '../../helper/style-loader'; +import { Icon, MynahIcons, MynahIconsType } from '../icon'; + +interface SelectOption { + value: string; + label?: string; + icon?: MynahIcons | MynahIconsType; +} + +export interface RadioGroupProps { + type?: 'radiogroup' | 'toggle'; + classNames?: string[]; + attributes?: Record; + label?: HTMLElement | ExtendedHTMLElement | string; + description?: ExtendedHTMLElement; + value?: string; + optional?: boolean; + options?: SelectOption[]; + onChange?: (value: string) => void; + wrapperTestId?: string; + optionTestId?: string; +} + +export abstract class RadioGroupAbstract { + render: ExtendedHTMLElement; + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; +} +export class RadioGroupInternal extends RadioGroupAbstract { + private readonly radioGroupElement: ExtendedHTMLElement; + private readonly groupName: string = generateUID(); + render: ExtendedHTMLElement; + constructor (props: RadioGroupProps) { + StyleLoader.getInstance().load('components/_form-input.scss'); + super(); + // Only add vertical classes for radiogroup type + const isRadioGroup = props.type === 'radiogroup'; + this.radioGroupElement = DomBuilder.getInstance().build({ + type: 'div', + testId: props.wrapperTestId, + classNames: [ + 'mynah-form-input', + ...(props.classNames ?? []), + ...(isRadioGroup ? [ 'mynah-form-input-vertical' ] : []) + ], + children: + props.options?.map((option, index) => ({ + type: 'div', + classNames: [ + 'mynah-form-input-radio-wrapper', + ...(isRadioGroup ? [ 'mynah-form-input-radio-wrapper-vertical' ] : []) + ], + children: [ { + type: 'label', + testId: props.optionTestId, + classNames: [ 'mynah-form-input-radio-label' ], + events: { + click: (e) => { + cancelEvent(e); + e.currentTarget.querySelector('input').checked = true; + this.setValue(option.value); + props.onChange?.(option.value); + } + }, + children: [ + { + type: 'input', + attributes: { + type: 'radio', + id: `${this.groupName}_${option.value}`, + name: this.groupName, + value: option.value, + ...( + (props.value !== undefined && props.value === option.value) || (props.optional !== true && props.value === undefined && index === 0) ? { checked: 'checked' } : {} + ) + }, + }, + { + type: 'span', + classNames: [ 'mynah-form-input-radio-check' ], + children: [ + new Icon({ icon: option.icon ?? MynahIcons.DOT }).render + ] + }, + ...(option.label != null + ? [ { + type: 'span', + children: [ option.label ] + } ] + : []) + ] + } ] + })) as DomBuilderObject[] + }); + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ ...(props.label != null ? [ props.label ] : []) ] + }, + ...[ props.description !== undefined ? props.description : '' ], + { + type: 'div', + classNames: [ 'mynah-form-input-container', `mynah-form-input-${props.type === 'radiogroup' ? 'radio' : 'toggle'}-group`, 'no-border' ], + ...(props.attributes !== undefined ? { attributes: props.attributes } : {}), + children: [ + this.radioGroupElement, + ] + } + ] + }); + } + + setValue = (value: string): void => { + this.radioGroupElement.querySelector('[checked]')?.removeAttribute('checked'); + this.radioGroupElement.querySelector(`[id="${this.groupName}_${value}"]`)?.setAttribute('checked', 'checked'); + }; + + getValue = (): string => { + return this.radioGroupElement.querySelector('[checked]')?.getAttribute('id')?.replace(`${this.groupName}_`, '') ?? ''; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.radioGroupElement.removeAttribute('disabled'); + this.radioGroupElement.querySelectorAll('input').forEach(inputElm => inputElm.removeAttribute('disabled')); + } else { + this.radioGroupElement.setAttribute('disabled', 'disabled'); + this.radioGroupElement.querySelectorAll('input').forEach(inputElm => inputElm.setAttribute('disabled', 'disabled')); + } + }; +} + +export class RadioGroup extends RadioGroupAbstract { + render: ExtendedHTMLElement; + + constructor (props: RadioGroupProps) { + super(); + return new (Config.getInstance().config.componentClasses.RadioGroup ?? RadioGroupInternal)(props); + } + + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; +} diff --git a/vendor/mynah-ui/src/components/form-items/select.ts b/vendor/mynah-ui/src/components/form-items/select.ts new file mode 100644 index 00000000..31c7b2d6 --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/select.ts @@ -0,0 +1,235 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { StyleLoader } from '../../helper/style-loader'; +import { Icon, MynahIcons, MynahIconsType } from '../icon'; +import { Card } from '../card/card'; +import { CardBody } from '../card/card-body'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; + +const TOOLTIP_DELAY = 350; + +interface SelectOption { + value: string; + label: string; + description?: string; +} + +export interface SelectProps { + classNames?: string[]; + attributes?: Record; + handleIcon?: MynahIcons | MynahIconsType; + border?: boolean; + icon?: MynahIcons | MynahIconsType; + label?: HTMLElement | ExtendedHTMLElement | string; + description?: ExtendedHTMLElement; + value?: string; + optional?: boolean; + autoWidth?: boolean; + options?: SelectOption[]; + placeholder?: string; + onChange?: (value: string) => void; + wrapperTestId?: string; + optionTestId?: string; + tooltip?: string; +} + +export abstract class SelectAbstract { + render: ExtendedHTMLElement; + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; +} + +export class SelectInternal { + private readonly props: SelectProps; + private readonly selectElement: ExtendedHTMLElement; + private readonly autoWidthSizer: ExtendedHTMLElement; + private readonly selectContainer: ExtendedHTMLElement; + private tooltipOverlay: Overlay | null = null; + private tooltipTimeout: ReturnType | null = null; + render: ExtendedHTMLElement; + constructor (props: SelectProps) { + this.props = props; + StyleLoader.getInstance().load('components/_form-input.scss'); + this.autoWidthSizer = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ 'select-auto-width-sizer' ], + children: [ this.props.options?.find(option => option.value === this.props.value)?.label ?? this.props.placeholder ?? '' ] + }); + this.selectElement = DomBuilder.getInstance().build({ + type: 'select', + testId: props.wrapperTestId, + classNames: [ 'mynah-form-input', ...(props.classNames ?? []), ...(props.autoWidth === true ? [ 'auto-width' ] : []) ], + events: { + change: (e) => { + const value = (e.currentTarget as HTMLSelectElement).value; + if (props.onChange !== undefined) { + props.onChange(value); + } + this.autoWidthSizer.update({ + children: [ this.props.options?.find(option => option.value === value)?.label ?? this.props.placeholder ?? '' ] + }); + } + }, + children: + [ ...(props.optional === true + ? [ { + label: props.placeholder ?? '...', + value: '', + description: undefined + } ] + : []), ...props.options ?? [] ].map(option => ({ + type: 'option', + testId: props.optionTestId, + classNames: option.value === '' ? [ 'empty-option' ] : [], + attributes: { + value: option.value, + }, + children: [ + option.label + ] + })) as DomBuilderObject[] + }); + if (props.value !== undefined) { + this.selectElement.value = props.value; + } + this.selectContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-container', ...(props.border === false ? [ 'no-border' ] : []) ], + ...(props.attributes !== undefined ? { attributes: props.attributes } : {}), + children: [ + ...(props.icon + ? [ new Icon({ icon: props.icon, classNames: [ 'mynah-form-input-icon' ] }).render ] + : []), + ...(props.autoWidth !== undefined ? [ this.autoWidthSizer ] : []), + this.selectElement, + new Icon({ icon: props.handleIcon ?? MynahIcons.DOWN_OPEN, classNames: [ 'mynah-select-handle' ] }).render ] + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ ...(props.label !== undefined ? [ props.label ] : []) ] + }, + ...[ props.description !== undefined ? props.description : '' ], + this.selectContainer + ] + }); + + // Add tooltip functionality + this.setupTooltip(); + } + + private readonly setupTooltip = (): void => { + this.selectContainer.update({ + events: { + mouseenter: () => { + const currentTooltip = this.getCurrentTooltip(); + if (currentTooltip != null && currentTooltip.trim() !== '') { + this.showTooltip(currentTooltip); + } + }, + mouseleave: () => { + this.hideTooltip(); + } + } + }); + }; + + private readonly getCurrentTooltip = (): string => { + const currentValue = this.selectElement.value; + const selectedOption = this.props.options?.find(option => option.value === currentValue); + + // If there's a selected option, show label and description; otherwise use the base tooltip + if (selectedOption?.description != null) { + return `${selectedOption.label}
${selectedOption.description}`; + } + return this.props.tooltip ?? ''; + }; + + setValue = (value: string): void => { + this.selectElement.value = value; + this.autoWidthSizer.update({ + children: [ this.props.options?.find(option => option.value === value)?.label ?? this.props.placeholder ?? '' ] + }); + }; + + getValue = (): string => { + return this.selectElement.value; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.selectElement.removeAttribute('disabled'); + } else { + this.selectElement.setAttribute('disabled', 'disabled'); + } + }; + + private readonly showTooltip = (content: string): void => { + if (content.trim() !== '') { + // Clear any existing timeout + if (this.tooltipTimeout !== null) { + clearTimeout(this.tooltipTimeout); + } + + this.tooltipTimeout = setTimeout(() => { + this.tooltipOverlay = new Overlay({ + background: true, + closeOnOutsideClick: false, + referenceElement: this.selectContainer, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + new Card({ + border: false, + children: [ + new CardBody({ + body: content + }).render + ] + }).render + ], + }); + }, TOOLTIP_DELAY); + } + }; + + private readonly hideTooltip = (): void => { + // Clear any pending timeout + if (this.tooltipTimeout !== null) { + clearTimeout(this.tooltipTimeout); + this.tooltipTimeout = null; + } + + // Close existing tooltip + if (this.tooltipOverlay !== null) { + this.tooltipOverlay.close(); + this.tooltipOverlay = null; + } + }; +} + +export class Select extends SelectAbstract { + render: ExtendedHTMLElement; + + constructor (props: SelectProps) { + super(); + return new (Config.getInstance().config.componentClasses.Select ?? SelectInternal)(props); + } + + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; +} diff --git a/vendor/mynah-ui/src/components/form-items/stars.ts b/vendor/mynah-ui/src/components/form-items/stars.ts new file mode 100644 index 00000000..4b94cd0e --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/stars.ts @@ -0,0 +1,101 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { StyleLoader } from '../../helper/style-loader'; +import { Icon, MynahIcons } from '../icon'; + +export type StarValues = 1 | 2 | 3 | 4 | 5; +export interface StarsProps { + classNames?: string[]; + attributes?: Record; + label?: HTMLElement | ExtendedHTMLElement | string; + description?: ExtendedHTMLElement; + value?: string; + onChange?: (value: string) => void; + initStar?: StarValues; + wrapperTestId?: string; + optionTestId?: string; +} +export class Stars { + private readonly starsContainer: ExtendedHTMLElement; + render: ExtendedHTMLElement; + + constructor (props: StarsProps) { + StyleLoader.getInstance().load('components/_form-input.scss'); + this.starsContainer = DomBuilder.getInstance().build({ + type: 'div', + testId: props.wrapperTestId, + classNames: [ 'mynah-feedback-form-stars-container' ], + attributes: { ...(props.value !== undefined && { 'selected-star': props.value?.toString() ?? '1' }) }, + children: Array(5) + .fill(undefined) + .map((n, index) => + DomBuilder.getInstance().build({ + type: 'div', + testId: props.optionTestId, + classNames: [ 'mynah-feedback-form-star', ...(props.value === (index + 1).toString() ? [ 'selected' ] : []) ], + events: { + click: (e: MouseEvent) => { + (this.starsContainer.querySelector('.selected') as ExtendedHTMLElement)?.removeClass( + 'selected' + ); + (e.currentTarget as ExtendedHTMLElement).addClass('selected'); + if (props.onChange !== undefined) { + props.onChange((index + 1).toString()); + } + this.setValue((index + 1) as StarValues); + }, + }, + attributes: { star: (index + 1).toString() }, + children: [ new Icon({ icon: MynahIcons.STAR }).render ], + }) + ), + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ ...(props.label !== undefined ? [ props.label ] : []) ] + }, + ...[ props.description !== undefined ? props.description : '' ], + { + type: 'div', + classNames: [ 'mynah-form-input-container', 'no-border' ], + ...(props.attributes !== undefined ? { attributes: props.attributes } : {}), + children: [ + { + type: 'div', + classNames: [ 'mynah-form-input', ...(props.classNames ?? []) ], + children: [ + this.starsContainer + ] + }, + ] + } + ] + }); + } + + setValue = (star: StarValues): void => { + this.starsContainer.setAttribute('selected-star', star.toString()); + }; + + getValue = (): string => { + return this.starsContainer.getAttribute('selected-star') ?? ''; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.starsContainer.parentElement?.removeAttribute('disabled'); + } else { + this.starsContainer.parentElement?.setAttribute('disabled', 'disabled'); + } + }; +} diff --git a/vendor/mynah-ui/src/components/form-items/switch.ts b/vendor/mynah-ui/src/components/form-items/switch.ts new file mode 100644 index 00000000..fb1670a4 --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/switch.ts @@ -0,0 +1,139 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { cancelEvent } from '../../helper/events'; +import { StyleLoader } from '../../helper/style-loader'; +import { Icon, MynahIcons, MynahIconsType } from '../icon'; + +export interface SwitchProps { + classNames?: string[]; + attributes?: Record; + title?: HTMLElement | ExtendedHTMLElement | string; + label?: string; + description?: ExtendedHTMLElement; + value?: 'true' | 'false'; + optional?: boolean; + icon?: MynahIcons | MynahIconsType; + onChange?: (value: 'true' | 'false') => void; + testId?: string; +} + +export abstract class SwitchAbstract { + render: ExtendedHTMLElement; + setValue = (value: 'true' | 'false'): void => {}; + getValue = (): 'true' | 'false' => 'false'; + setEnabled = (enabled: boolean): void => {}; +} +export class SwitchInternal extends SwitchAbstract { + private readonly checkboxWrapper: ExtendedHTMLElement; + private readonly checkboxItem: ExtendedHTMLElement; + render: ExtendedHTMLElement; + constructor (props: SwitchProps) { + StyleLoader.getInstance().load('components/_form-input.scss'); + StyleLoader.getInstance().load('components/form-items/_switch.scss'); + super(); + this.checkboxItem = DomBuilder.getInstance().build({ + type: 'input', + attributes: { + type: 'checkbox', + }, + }); + this.checkboxItem.checked = props.value === 'true'; + + this.checkboxWrapper = DomBuilder.getInstance().build({ + type: 'div', + testId: props.testId, + classNames: [ 'mynah-form-input', ...(props.classNames ?? []) ], + children: [ + { + type: 'div', + classNames: [ 'mynah-form-input-switch-wrapper' ], + children: [ { + type: 'label', + classNames: [ 'mynah-form-input-switch-label' ], + events: { + click: (e) => { + cancelEvent(e); + const checkState = (!this.checkboxItem.checked).toString() as 'true' | 'false'; + this.setValue(checkState); + props.onChange?.(checkState); + } + }, + children: [ + this.checkboxItem, + { + type: 'span', + classNames: [ 'mynah-form-input-switch-check' ], + children: [ + new Icon({ icon: props.icon ?? MynahIcons.OK }).render, + ] + }, + { type: 'div', classNames: [ 'mynah-form-input-switch-check-bg' ] } + ] + }, + ...(props.label != null + ? [ { + type: 'span', + children: [ props.label ] + } ] + : []) + ] + } ] + }); + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ ...(props.title != null ? [ props.title ] : []) ] + }, + ...[ props.description !== undefined ? props.description : '' ], + { + type: 'div', + classNames: [ 'mynah-form-input-container', 'no-border' ], + ...(props.attributes !== undefined ? { attributes: props.attributes } : {}), + children: [ + this.checkboxWrapper, + ] + } + ] + }); + } + + setValue = (value: 'true' | 'false'): void => { + this.checkboxItem.checked = value === 'true'; + }; + + getValue = (): 'true' | 'false' => { + return this.checkboxItem.checked.toString() as 'true' | 'false'; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.checkboxWrapper.removeAttribute('disabled'); + this.checkboxItem.removeAttribute('disabled'); + } else { + this.checkboxWrapper.setAttribute('disabled', 'disabled'); + this.checkboxItem.setAttribute('disabled', 'disabled'); + } + }; +} + +export class Switch extends SwitchAbstract { + render: ExtendedHTMLElement; + + constructor (props: SwitchProps) { + super(); + return new (Config.getInstance().config.componentClasses.Switch ?? SwitchInternal)(props); + } + + setValue = (value: 'true' | 'false'): void => {}; + getValue = (): 'true' | 'false' => 'false'; + setEnabled = (enabled: boolean): void => {}; +} diff --git a/vendor/mynah-ui/src/components/form-items/text-area.ts b/vendor/mynah-ui/src/components/form-items/text-area.ts new file mode 100644 index 00000000..5a1917d0 --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/text-area.ts @@ -0,0 +1,151 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { StyleLoader } from '../../helper/style-loader'; +import { checkTextElementValidation } from '../../helper/validator'; +import { ValidationPattern } from '../../static'; + +export interface TextAreaProps { + classNames?: string[]; + attributes?: Record; + label?: HTMLElement | ExtendedHTMLElement | string; + autoFocus?: boolean; + description?: ExtendedHTMLElement; + mandatory?: boolean; + fireModifierAndEnterKeyPress?: () => void; + placeholder?: string; + validationPatterns?: { + operator?: 'and' | 'or'; + patterns: ValidationPattern[]; + }; + value?: string; + onChange?: (value: string) => void; + onKeyPress?: (event: KeyboardEvent) => void; + testId?: string; +} + +export abstract class TextAreaAbstract { + render: ExtendedHTMLElement; + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; + checkValidation = (): void => {}; +} +export class TextAreaInternal extends TextAreaAbstract { + private readonly inputElement: ExtendedHTMLElement; + private readonly validationErrorBlock: ExtendedHTMLElement; + private readonly props: TextAreaProps; + private readyToValidate: boolean = false; + render: ExtendedHTMLElement; + constructor (props: TextAreaProps) { + StyleLoader.getInstance().load('components/_form-input.scss'); + super(); + this.props = props; + this.validationErrorBlock = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-validation-error-block' ], + }); + this.inputElement = DomBuilder.getInstance().build({ + type: 'textarea', + testId: this.props.testId, + classNames: [ 'mynah-form-input', ...(this.props.classNames ?? []) ], + attributes: { + ...(this.props.placeholder !== undefined + ? { + placeholder: this.props.placeholder + } + : {}), + ...(this.props.autoFocus === true + ? { + autofocus: 'autofocus' + } + : {}), + }, + events: { + blur: (e) => { + this.readyToValidate = true; + this.checkValidation(); + }, + // TODO: change this to 'input' event? + keyup: (e) => { + if (this.props.onChange !== undefined) { + this.props.onChange((e.currentTarget as HTMLTextAreaElement).value); + } + this.checkValidation(); + }, + keydown: (e: KeyboardEvent) => { + if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { + this.props.fireModifierAndEnterKeyPress?.(); + } + }, + keypress: (e: KeyboardEvent) => { + this.props.onKeyPress?.(e); + } + }, + }); + this.inputElement.value = props.value ?? ''; + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ ...(props.label !== undefined ? [ props.label ] : []) ] + }, + ...[ props.description !== undefined ? props.description : '' ], + { + type: 'div', + classNames: [ 'mynah-form-input-container' ], + ...(props.attributes !== undefined ? { attributes: props.attributes } : {}), + children: [ + this.inputElement, + ] + }, + this.validationErrorBlock + ] + }); + + if (this.props.autoFocus === true) { + setTimeout(() => { + this.inputElement.focus(); + }, 250); + } + } + + setValue = (value: string): void => { + this.inputElement.value = value; + }; + + getValue = (): string => { + return this.inputElement.value; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.inputElement.removeAttribute('disabled'); + } else { + this.inputElement.setAttribute('disabled', 'disabled'); + } + }; + + checkValidation = (): void => checkTextElementValidation(this.inputElement, this.props.validationPatterns, this.validationErrorBlock, this.readyToValidate, this.props.mandatory); +} + +export class TextArea extends TextAreaAbstract { + render: ExtendedHTMLElement; + + constructor (props: TextAreaProps) { + super(); + return new (Config.getInstance().config.componentClasses.TextArea ?? TextAreaInternal)(props); + } + + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; + checkValidation = (): void => {}; +} diff --git a/vendor/mynah-ui/src/components/form-items/text-input.ts b/vendor/mynah-ui/src/components/form-items/text-input.ts new file mode 100644 index 00000000..f08f12c0 --- /dev/null +++ b/vendor/mynah-ui/src/components/form-items/text-input.ts @@ -0,0 +1,166 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { StyleLoader } from '../../helper/style-loader'; +import { checkTextElementValidation } from '../../helper/validator'; +import { ValidationPattern } from '../../static'; +import { Icon, MynahIcons, MynahIconsType } from '../icon'; + +export interface TextInputProps { + classNames?: string[]; + attributes?: Record; + label?: HTMLElement | ExtendedHTMLElement | string; + autoFocus?: boolean; + description?: ExtendedHTMLElement; + icon?: MynahIcons | MynahIconsType; + mandatory?: boolean; + fireModifierAndEnterKeyPress?: () => void; + placeholder?: string; + type?: 'text' | 'number' | 'email'; + validationPatterns?: { + operator?: 'and' | 'or'; + patterns: ValidationPattern[]; + }; + validateOnChange?: boolean; + value?: string; + onChange?: (value: string) => void; + onKeyPress?: (event: KeyboardEvent) => void; + testId?: string; +} + +export abstract class TextInputAbstract { + render: ExtendedHTMLElement; + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; + checkValidation = (): void => {}; +} +export class TextInputInternal extends TextInputAbstract { + private readonly inputElement: ExtendedHTMLElement; + private readonly validationErrorBlock: ExtendedHTMLElement; + private readonly props: TextInputProps; + private readyToValidate: boolean = false; + private touched: boolean = false; + render: ExtendedHTMLElement; + constructor (props: TextInputProps) { + StyleLoader.getInstance().load('components/_form-input.scss'); + super(); + this.props = props; + this.validationErrorBlock = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-validation-error-block' ], + }); + this.inputElement = DomBuilder.getInstance().build({ + type: 'input', + testId: this.props.testId, + classNames: [ 'mynah-form-input', ...(this.props.classNames ?? []) ], + attributes: { + type: props.type ?? 'text', + ...(this.props.placeholder !== undefined + ? { + placeholder: this.props.placeholder + } + : {}), + ...(this.props.autoFocus === true + ? { + autofocus: 'autofocus' + } + : {}), + }, + events: { + blur: (e) => { + // Only show validation error if user changed the input + if (this.touched) { + this.readyToValidate = true; + } + this.checkValidation(); + }, + input: (e) => { + if (this.props.onChange !== undefined) { + this.props.onChange((e.currentTarget as HTMLInputElement).value); + } + if (props.validateOnChange === true) { + this.readyToValidate = true; + } + this.touched = true; + this.checkValidation(); + }, + keydown: (e: KeyboardEvent) => { + if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { + this.props.fireModifierAndEnterKeyPress?.(); + } + }, + keypress: (e: KeyboardEvent) => { + this.props.onKeyPress?.(e); + } + }, + }); + this.inputElement.value = props.value?.toString() ?? ''; + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-form-input-wrapper' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-form-input-label' ], + children: [ ...(props.label !== undefined ? [ props.label ] : []) ] + }, + ...[ props.description !== undefined ? props.description : '' ], + { + type: 'div', + classNames: [ 'mynah-form-input-container' ], + ...(props.attributes !== undefined ? { attributes: props.attributes } : {}), + children: [ + ...(props.icon + ? [ new Icon({ icon: props.icon, classNames: [ 'mynah-form-input-icon' ] }).render ] + : []), + this.inputElement, + ] + }, + this.validationErrorBlock + ] + }); + + if (this.props.autoFocus === true) { + setTimeout(() => { + this.inputElement.focus(); + }, 250); + } + } + + setValue = (value: string): void => { + this.inputElement.value = value; + }; + + getValue = (): string => { + return this.inputElement.value; + }; + + setEnabled = (enabled: boolean): void => { + if (enabled) { + this.inputElement.removeAttribute('disabled'); + } else { + this.inputElement.setAttribute('disabled', 'disabled'); + } + }; + + checkValidation = (): void => checkTextElementValidation(this.inputElement, this.props.validationPatterns, this.validationErrorBlock, this.readyToValidate, this.props.mandatory); +} + +export class TextInput extends TextInputAbstract { + render: ExtendedHTMLElement; + + constructor (props: TextInputProps) { + super(); + return new (Config.getInstance().config.componentClasses.TextInput ?? TextInputInternal)(props); + } + + setValue = (value: string): void => {}; + getValue = (): string => ''; + setEnabled = (enabled: boolean): void => {}; + checkValidation = (): void => {}; +} diff --git a/vendor/mynah-ui/src/components/icon.ts b/vendor/mynah-ui/src/components/icon.ts new file mode 100644 index 00000000..9afb8cf3 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon.ts @@ -0,0 +1,151 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { StyleLoader } from '../helper/style-loader'; +import { MynahUIIconImporter } from './icon/icon-importer'; +import '../styles/components/_icon.scss'; +import { Status } from '../static'; + +export enum MynahIcons { + Q = 'q', + AT = 'at', + MENU = 'menu', + MINUS = 'minus', + MINUS_CIRCLE = 'minus-circled', + SEARCH = 'search', + PLUS = 'plus', + PAPER_CLIP = 'paper-clip', + PIN = 'pin', + LIST_ADD = 'list-add', + TABS = 'tabs', + CHAT = 'chat', + LINK = 'link', + FOLDER = 'folder', + FILE = 'file', + FLASH = 'flash', + DOC = 'doc', + DOT = 'dot', + EXTERNAL = 'external', + CANCEL = 'cancel', + CANCEL_CIRCLE = 'cancel-circle', + CALENDAR = 'calendar', + COMMENT = 'comment', + MEGAPHONE = 'megaphone', + MAGIC = 'magic', + NOTIFICATION = 'notification', + EYE = 'eye', + ELLIPSIS = 'ellipsis', + ELLIPSIS_H = 'ellipsis-h', + OK = 'ok', + UP_OPEN = 'up-open', + DOWN_OPEN = 'down-open', + RIGHT_OPEN = 'right-open', + LEFT_OPEN = 'left-open', + RESIZE_FULL = 'resize-full', + RESIZE_SMALL = 'resize-small', + BLOCK = 'block', + OK_CIRCLED = 'ok-circled', + INFO = 'info', + WARNING = 'warning', + ERROR = 'error', + THUMBS_UP = 'thumbs-up', + THUMBS_DOWN = 'thumbs-down', + STAR = 'star', + STACK = 'stack', + LIGHT_BULB = 'light-bulb', + ENVELOPE_SEND = 'envelope-send', + ENTER = 'enter', + REFRESH = 'refresh', + PROGRESS = 'progress', + SCROLL_DOWN = 'scroll-down', + USER = 'user', + PLAY = 'play', + PAUSE = 'pause', + STOP = 'stop', + PENCIL = 'pencil', + CODE_BLOCK = 'code-block', + COPY = 'copy', + CURSOR_INSERT = 'cursor-insert', + TEXT_SELECT = 'text-select', + TOOLS = 'tools', + REVERT = 'revert', + UNDO = 'undo', + ROCKET = 'rocket', + ASTERISK = 'asterisk', + BUG = 'bug', + CHECK_LIST = 'check-list', + DEPLOY = 'deploy', + SHELL = 'shell', + HELP = 'help', + MESSAGE = 'message', + MCP = 'mcp', + TRASH = 'trash', + TRANSFORM = 'transform', + HISTORY = 'history', + IMAGE = 'image', + DESKTOP = 'desktop', + GLOBE = 'globe' +} + +export interface CustomIcon { + name: string; + base64Svg: string; +} + +export type MynahIconsType = `${MynahIcons}`; + +export interface IconProps { + icon: MynahIcons | MynahIconsType | CustomIcon; + subtract?: boolean; + classNames?: string[]; + status?: Status; +} +export class Icon { + render: ExtendedHTMLElement; + props: IconProps; + constructor (props: IconProps) { + this.props = props; + StyleLoader.getInstance().load('components/_icon.scss'); + MynahUIIconImporter.getInstance(); + + // Determine if the icon is a custom icon or a predefined one + const iconName = this.getIconName(); + + this.render = DomBuilder.getInstance().build({ + type: 'i', + classNames: [ + 'mynah-ui-icon', + `mynah-ui-icon-${iconName}${props.subtract === true ? '-subtract' : ''}`, + ...(props.status !== undefined ? [ `status-${props.status}` ] : []), + ...(props.classNames !== undefined ? props.classNames : []), + ] + }); + } + + private readonly getIconName = (): string => { + // If it's a custom icon, register it first + if (this.isCustomIcon(this.props.icon)) { + MynahUIIconImporter.getInstance().addCustomIcon(this.props.icon); + } + + return this.isCustomIcon(this.props.icon) + ? this.props.icon.name + : this.props.icon; + }; + + private isCustomIcon (icon: MynahIcons | MynahIconsType | CustomIcon): icon is CustomIcon { + return typeof icon === 'object' && 'base64Svg' in icon && 'name' in icon; + } + + public update = (icon: MynahIcons | MynahIconsType | CustomIcon): void => { + const oldIconName = this.getIconName(); + this.render.removeClass(`mynah-ui-icon-${oldIconName}${this.props.subtract === true ? '-subtract' : ''}`); + + this.props.icon = icon; + const newIconName = this.getIconName(); + this.render.addClass(`mynah-ui-icon-${newIconName}${this.props.subtract === true ? '-subtract' : ''}`); + }; +} diff --git a/vendor/mynah-ui/src/components/icon/icon-importer.ts b/vendor/mynah-ui/src/components/icon/icon-importer.ts new file mode 100644 index 00000000..2b4419b9 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icon-importer.ts @@ -0,0 +1,240 @@ +/* eslint-disable @typescript-eslint/no-extraneous-class */ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder } from '../../helper/dom'; +import { CustomIcon, MynahIcons } from '../icon'; +// ICONS +import Q from './icons/q.svg'; +import DOT from './icons/dot.svg'; +import AT from './icons/at.svg'; +import MENU from './icons/menu.svg'; +import MINUS from './icons/minus.svg'; +import MINUS_CIRCLE from './icons/minus-circled.svg'; +import SEARCH from './icons/search.svg'; +import PLUS from './icons/plus.svg'; +import PAPER_CLIP from './icons/paper-clip.svg'; +import LIST_ADD from './icons/list-add.svg'; +import TABS from './icons/tabs.svg'; +import PENCIL from './icons/pencil.svg'; +import FOLDER from './icons/folder.svg'; +import FILE from './icons/file.svg'; +import FLASH from './icons/flash.svg'; +import CHAT from './icons/chat.svg'; +import COMMENT from './icons/comment.svg'; +import LINK from './icons/link.svg'; +import DOC from './icons/doc.svg'; +import EXTERNAL from './icons/external.svg'; +import CANCEL from './icons/cancel.svg'; +import CANCEL_CIRCLE from './icons/cancel-circle.svg'; +import CALENDAR from './icons/calendar.svg'; +import MEGAPHONE from './icons/megaphone.svg'; +import MAGIC from './icons/magic.svg'; +import NOTIFICATION from './icons/notification.svg'; +import EYE from './icons/eye.svg'; +import ELLIPSIS from './icons/ellipsis.svg'; +import ELLIPSIS_H from './icons/ellipsis-h.svg'; +import OK from './icons/ok.svg'; +import UP_OPEN from './icons/up-open.svg'; +import DOWN_OPEN from './icons/down-open.svg'; +import RIGHT_OPEN from './icons/right-open.svg'; +import LEFT_OPEN from './icons/left-open.svg'; +import RESIZE_FULL from './icons/resize-full.svg'; +import RESIZE_SMALL from './icons/resize-small.svg'; +import BLOCK from './icons/block.svg'; +import OK_CIRCLED from './icons/ok-circled.svg'; +import INFO from './icons/info.svg'; +import WARNING from './icons/warning.svg'; +import ERROR from './icons/error.svg'; +import THUMBS_UP from './icons/thumbs-up.svg'; +import THUMBS_DOWN from './icons/thumbs-down.svg'; +import PIN from './icons/pin.svg'; +import STAR from './icons/star.svg'; +import STACK from './icons/stack.svg'; +import LIGHT_BULB from './icons/light-bulb.svg'; +import ENVELOPE_SEND from './icons/envelope-send.svg'; +import ENTER from './icons/enter.svg'; +import REFRESH from './icons/refresh.svg'; +import PROGRESS from './icons/progress.svg'; +import SCROLL_DOWN from './icons/scroll-down.svg'; +import USER from './icons/user.svg'; +import PLAY from './icons/play.svg'; +import PAUSE from './icons/pause.svg'; +import STOP from './icons/stop.svg'; +import CODE_BLOCK from './icons/code-block.svg'; +import COPY from './icons/copy.svg'; +import CURSOR_INSERT from './icons/cursor-insert.svg'; +import TEXT_SELECT from './icons/text-select.svg'; +import TOOLS from './icons/tools.svg'; +import REVERT from './icons/revert.svg'; +import UNDO from './icons/undo.svg'; +import ROCKET from './icons/rocket.svg'; +import ASTERISK from './icons/asterisk.svg'; +import BUG from './icons/bug.svg'; +import CHECK_LIST from './icons/check-list.svg'; +import DEPLOY from './icons/deploy.svg'; +import SHELL from './icons/shell.svg'; +import HELP from './icons/help.svg'; +import HISTORY from './icons/history.svg'; +import MESSAGE from './icons/message.svg'; +import MCP from './icons/mcp.svg'; +import TRASH from './icons/trash.svg'; +import TRANSFORM from './icons/transform.svg'; +import IMAGE from './icons/image.svg'; +import DESKTOP from './icons/desktop.svg'; +import GLOBE from './icons/globe.svg'; + +export class MynahUIIconImporter { + private static instance: MynahUIIconImporter; + private readonly customIcons: Map = new Map(); + private readonly portalId = 'mynah-ui-icons'; + private readonly defaultIconMappings = { + Q, + DOT, + AT, + MENU, + MINUS, + MINUS_CIRCLE, + SEARCH, + PLUS, + PAPER_CLIP, + LIST_ADD, + FOLDER, + FILE, + FLASH, + TABS, + PENCIL, + CHAT, + LINK, + DOC, + EXTERNAL, + CANCEL, + CANCEL_CIRCLE, + CALENDAR, + COMMENT, + MEGAPHONE, + MAGIC, + NOTIFICATION, + EYE, + ELLIPSIS, + ELLIPSIS_H, + OK, + UP_OPEN, + DOWN_OPEN, + RIGHT_OPEN, + LEFT_OPEN, + RESIZE_FULL, + RESIZE_SMALL, + BLOCK, + OK_CIRCLED, + INFO, + WARNING, + ERROR, + THUMBS_UP, + THUMBS_DOWN, + STAR, + STACK, + LIGHT_BULB, + ENVELOPE_SEND, + ENTER, + REFRESH, + PROGRESS, + SCROLL_DOWN, + USER, + PLAY, + PAUSE, + STOP, + CODE_BLOCK, + COPY, + CURSOR_INSERT, + TEXT_SELECT, + TOOLS, + REVERT, + UNDO, + ROCKET, + ASTERISK, + BUG, + CHECK_LIST, + DEPLOY, + SHELL, + HELP, + MESSAGE, + MCP, + TRASH, + TRANSFORM, + HISTORY, + IMAGE, + PIN, + DESKTOP, + GLOBE + }; + + private constructor () { + this.initializeDefaultIcons(); + } + + private cleanupExistingPortal (): void { + const existingPortal = document.getElementById(this.portalId); + if (existingPortal != null) { + existingPortal.remove(); + } + } + + private initializeDefaultIcons (): void { + this.createIconStyles(this.defaultIconMappings); + } + + public addCustomIcon (customIcon: CustomIcon): void { + // If icon already exists with same content, no need to proceed + if (this.customIcons.get(customIcon.name) === customIcon.base64Svg) { + return; + } + + this.customIcons.set(customIcon.name, customIcon.base64Svg); + + // Recreate all styles including both default and custom icons + this.cleanupExistingPortal(); + this.createIconStyles({ + ...this.defaultIconMappings, + ...Object.fromEntries(this.customIcons) + }); + } + + private createIconStyles (iconMappings: Record): void { + DomBuilder.getInstance().createPortal('mynah-ui-icons', { + type: 'style', + attributes: { + type: 'text/css' + }, + children: [ ` + ${Object.keys(iconMappings).map(iconKey => { + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + const iconName = MynahIcons[iconKey as keyof typeof MynahIcons] || iconKey; + return ` + :root{ + --mynah-ui-icon-${iconName}: url(${iconMappings[iconKey]}); + } + .mynah-ui-icon-${iconName} { + -webkit-mask-image: var(--mynah-ui-icon-${iconName}); + mask-image: var(--mynah-ui-icon-${iconName}); + } + .mynah-ui-icon-${iconName}-subtract { + -webkit-mask-image: linear-gradient(#000000, #000000), var(--mynah-ui-icon-${iconName}); + mask-image: linear-gradient(#000000, #000000), var(--mynah-ui-icon-${iconName}); + mask-composite: subtract; + }`; + }).join('')} + ` ] + }, 'beforebegin'); + } + + public static getInstance = (): MynahUIIconImporter => { + if (MynahUIIconImporter.instance === undefined) { + MynahUIIconImporter.instance = new MynahUIIconImporter(); + } + + return MynahUIIconImporter.instance; + }; +} diff --git a/vendor/mynah-ui/src/components/icon/icons/asterisk.svg b/vendor/mynah-ui/src/components/icon/icons/asterisk.svg new file mode 100644 index 00000000..b91dc2dc --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/asterisk.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/at.svg b/vendor/mynah-ui/src/components/icon/icons/at.svg new file mode 100644 index 00000000..b25c043d --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/at.svg @@ -0,0 +1,7 @@ + + + Layer 1 + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/block.svg b/vendor/mynah-ui/src/components/icon/icons/block.svg new file mode 100644 index 00000000..47b8db5d --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/block.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/bug.svg b/vendor/mynah-ui/src/components/icon/icons/bug.svg new file mode 100644 index 00000000..1834ede4 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/bug.svg @@ -0,0 +1,7 @@ + + + Layer 1 + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/calendar.svg b/vendor/mynah-ui/src/components/icon/icons/calendar.svg new file mode 100644 index 00000000..83f3e142 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/calendar.svg @@ -0,0 +1,25 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/cancel-circle.svg b/vendor/mynah-ui/src/components/icon/icons/cancel-circle.svg new file mode 100644 index 00000000..840b6c15 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/cancel-circle.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/cancel.svg b/vendor/mynah-ui/src/components/icon/icons/cancel.svg new file mode 100644 index 00000000..4923bbfd --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/cancel.svg @@ -0,0 +1,7 @@ + + + Layer 1 + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/chat.svg b/vendor/mynah-ui/src/components/icon/icons/chat.svg new file mode 100644 index 00000000..6d664dcf --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/chat.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/check-list.svg b/vendor/mynah-ui/src/components/icon/icons/check-list.svg new file mode 100644 index 00000000..966c6cf6 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/check-list.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/code-block.svg b/vendor/mynah-ui/src/components/icon/icons/code-block.svg new file mode 100644 index 00000000..2c81a2a9 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/code-block.svg @@ -0,0 +1,13 @@ + + + + + + + + Layer 1 + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/comment.svg b/vendor/mynah-ui/src/components/icon/icons/comment.svg new file mode 100644 index 00000000..4f24d757 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/comment.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/copy.svg b/vendor/mynah-ui/src/components/icon/icons/copy.svg new file mode 100644 index 00000000..c28e4fcf --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/cursor-insert.svg b/vendor/mynah-ui/src/components/icon/icons/cursor-insert.svg new file mode 100644 index 00000000..a7415322 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/cursor-insert.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/deploy.svg b/vendor/mynah-ui/src/components/icon/icons/deploy.svg new file mode 100644 index 00000000..1110b633 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/deploy.svg @@ -0,0 +1,7 @@ + + + Layer 1 + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/desktop.svg b/vendor/mynah-ui/src/components/icon/icons/desktop.svg new file mode 100644 index 00000000..9abc8bbd --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/desktop.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/doc.svg b/vendor/mynah-ui/src/components/icon/icons/doc.svg new file mode 100644 index 00000000..d76f6830 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/doc.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/dot.svg b/vendor/mynah-ui/src/components/icon/icons/dot.svg new file mode 100644 index 00000000..be4da433 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/dot.svg @@ -0,0 +1,7 @@ + + + Layer 1 + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/down-open.svg b/vendor/mynah-ui/src/components/icon/icons/down-open.svg new file mode 100644 index 00000000..5dbfcb24 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/down-open.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/ellipsis-h.svg b/vendor/mynah-ui/src/components/icon/icons/ellipsis-h.svg new file mode 100644 index 00000000..7ca0ad15 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/ellipsis-h.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/ellipsis.svg b/vendor/mynah-ui/src/components/icon/icons/ellipsis.svg new file mode 100644 index 00000000..4fa6ab65 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/ellipsis.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/enter.svg b/vendor/mynah-ui/src/components/icon/icons/enter.svg new file mode 100644 index 00000000..a72172ec --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/enter.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/envelope-send.svg b/vendor/mynah-ui/src/components/icon/icons/envelope-send.svg new file mode 100644 index 00000000..349a988d --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/envelope-send.svg @@ -0,0 +1,6 @@ + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/error.svg b/vendor/mynah-ui/src/components/icon/icons/error.svg new file mode 100644 index 00000000..553dd110 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/error.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/external.svg b/vendor/mynah-ui/src/components/icon/icons/external.svg new file mode 100644 index 00000000..5fee1748 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/external.svg @@ -0,0 +1,4 @@ + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/eye.svg b/vendor/mynah-ui/src/components/icon/icons/eye.svg new file mode 100644 index 00000000..67979791 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/file.svg b/vendor/mynah-ui/src/components/icon/icons/file.svg new file mode 100644 index 00000000..2f57827e --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/file.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/flash.svg b/vendor/mynah-ui/src/components/icon/icons/flash.svg new file mode 100644 index 00000000..b8108fd1 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/flash.svg @@ -0,0 +1,2 @@ + +ionicons-v5-m \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/folder.svg b/vendor/mynah-ui/src/components/icon/icons/folder.svg new file mode 100644 index 00000000..cfaa45e4 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/folder.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/globe.svg b/vendor/mynah-ui/src/components/icon/icons/globe.svg new file mode 100644 index 00000000..375a1a39 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/globe.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/help.svg b/vendor/mynah-ui/src/components/icon/icons/help.svg new file mode 100644 index 00000000..ed0bd5bf --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/help.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/history.svg b/vendor/mynah-ui/src/components/icon/icons/history.svg new file mode 100644 index 00000000..67a20c61 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/history.svg @@ -0,0 +1,4 @@ + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/image.svg b/vendor/mynah-ui/src/components/icon/icons/image.svg new file mode 100644 index 00000000..e79497ee --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/image.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/info.svg b/vendor/mynah-ui/src/components/icon/icons/info.svg new file mode 100644 index 00000000..15476ec1 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/left-open.svg b/vendor/mynah-ui/src/components/icon/icons/left-open.svg new file mode 100644 index 00000000..68bd6d38 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/left-open.svg @@ -0,0 +1,11 @@ + + Created with Fabric.js 5.2.4 + + + Layer 1 + + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/light-bulb.svg b/vendor/mynah-ui/src/components/icon/icons/light-bulb.svg new file mode 100644 index 00000000..35b5fde4 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/light-bulb.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/link.svg b/vendor/mynah-ui/src/components/icon/icons/link.svg new file mode 100644 index 00000000..5ddea6a8 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/link.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/list-add.svg b/vendor/mynah-ui/src/components/icon/icons/list-add.svg new file mode 100644 index 00000000..c8fd254c --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/list-add.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/magic.svg b/vendor/mynah-ui/src/components/icon/icons/magic.svg new file mode 100644 index 00000000..1ef43935 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/magic.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/mcp.svg b/vendor/mynah-ui/src/components/icon/icons/mcp.svg new file mode 100644 index 00000000..4feb8401 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/mcp.svg @@ -0,0 +1,10 @@ + + + + Layer 1 + + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/megaphone.svg b/vendor/mynah-ui/src/components/icon/icons/megaphone.svg new file mode 100644 index 00000000..c8a49cda --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/megaphone.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/menu.svg b/vendor/mynah-ui/src/components/icon/icons/menu.svg new file mode 100644 index 00000000..01f3fea1 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/menu.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/message.svg b/vendor/mynah-ui/src/components/icon/icons/message.svg new file mode 100644 index 00000000..cb4e61e7 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/message.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/minus-circled.svg b/vendor/mynah-ui/src/components/icon/icons/minus-circled.svg new file mode 100644 index 00000000..a8c88b57 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/minus-circled.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/minus.svg b/vendor/mynah-ui/src/components/icon/icons/minus.svg new file mode 100644 index 00000000..5e704b95 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/minus.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/notification.svg b/vendor/mynah-ui/src/components/icon/icons/notification.svg new file mode 100644 index 00000000..d2053b30 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/notification.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/ok-circled.svg b/vendor/mynah-ui/src/components/icon/icons/ok-circled.svg new file mode 100644 index 00000000..5f72af1f --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/ok-circled.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/ok.svg b/vendor/mynah-ui/src/components/icon/icons/ok.svg new file mode 100644 index 00000000..ce9f2366 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/ok.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/paper-clip.svg b/vendor/mynah-ui/src/components/icon/icons/paper-clip.svg new file mode 100644 index 00000000..ade8ce95 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/paper-clip.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/pause.svg b/vendor/mynah-ui/src/components/icon/icons/pause.svg new file mode 100644 index 00000000..dcb67d35 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/pause.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/pencil.svg b/vendor/mynah-ui/src/components/icon/icons/pencil.svg new file mode 100644 index 00000000..7cf4ff0f --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/pencil.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/pin.svg b/vendor/mynah-ui/src/components/icon/icons/pin.svg new file mode 100644 index 00000000..3039879e --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/pin.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/play.svg b/vendor/mynah-ui/src/components/icon/icons/play.svg new file mode 100644 index 00000000..4cf02a3a --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/play.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/plus.svg b/vendor/mynah-ui/src/components/icon/icons/plus.svg new file mode 100644 index 00000000..873f4fac --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/progress.svg b/vendor/mynah-ui/src/components/icon/icons/progress.svg new file mode 100644 index 00000000..7b1291f1 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/progress.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/q.svg b/vendor/mynah-ui/src/components/icon/icons/q.svg new file mode 100644 index 00000000..068ba16a --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/q.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/refresh.svg b/vendor/mynah-ui/src/components/icon/icons/refresh.svg new file mode 100644 index 00000000..c455505a --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/refresh.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/resize-full.svg b/vendor/mynah-ui/src/components/icon/icons/resize-full.svg new file mode 100644 index 00000000..40e34946 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/resize-full.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/resize-small.svg b/vendor/mynah-ui/src/components/icon/icons/resize-small.svg new file mode 100644 index 00000000..bbd78784 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/resize-small.svg @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/revert.svg b/vendor/mynah-ui/src/components/icon/icons/revert.svg new file mode 100644 index 00000000..27fc9c59 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/revert.svg @@ -0,0 +1,6 @@ + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/right-open.svg b/vendor/mynah-ui/src/components/icon/icons/right-open.svg new file mode 100644 index 00000000..efaf299f --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/right-open.svg @@ -0,0 +1,14 @@ + +Created with Fabric.js 5.2.4 + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/rocket.svg b/vendor/mynah-ui/src/components/icon/icons/rocket.svg new file mode 100644 index 00000000..65f6fbb3 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/rocket.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/scroll-down.svg b/vendor/mynah-ui/src/components/icon/icons/scroll-down.svg new file mode 100644 index 00000000..a25ba154 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/scroll-down.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/search.svg b/vendor/mynah-ui/src/components/icon/icons/search.svg new file mode 100644 index 00000000..f9af1622 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/search.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/shell.svg b/vendor/mynah-ui/src/components/icon/icons/shell.svg new file mode 100644 index 00000000..db314aea --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/shell.svg @@ -0,0 +1,7 @@ + + + Layer 1 + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/stack.svg b/vendor/mynah-ui/src/components/icon/icons/stack.svg new file mode 100644 index 00000000..026cbb43 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/stack.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/star.svg b/vendor/mynah-ui/src/components/icon/icons/star.svg new file mode 100644 index 00000000..d024a017 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/star.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/stop.svg b/vendor/mynah-ui/src/components/icon/icons/stop.svg new file mode 100644 index 00000000..b1cd5114 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/stop.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/tabs.svg b/vendor/mynah-ui/src/components/icon/icons/tabs.svg new file mode 100644 index 00000000..75090e66 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/tabs.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/text-select.svg b/vendor/mynah-ui/src/components/icon/icons/text-select.svg new file mode 100644 index 00000000..fb060b8c --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/text-select.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/thumbs-down.svg b/vendor/mynah-ui/src/components/icon/icons/thumbs-down.svg new file mode 100644 index 00000000..6638bcd8 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/thumbs-down.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/thumbs-up.svg b/vendor/mynah-ui/src/components/icon/icons/thumbs-up.svg new file mode 100644 index 00000000..d759c595 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/thumbs-up.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/tools.svg b/vendor/mynah-ui/src/components/icon/icons/tools.svg new file mode 100644 index 00000000..723c1de3 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/tools.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/transform.svg b/vendor/mynah-ui/src/components/icon/icons/transform.svg new file mode 100644 index 00000000..c44cd2cf --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/transform.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/trash.svg b/vendor/mynah-ui/src/components/icon/icons/trash.svg new file mode 100644 index 00000000..1acda436 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/trash.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/undo.svg b/vendor/mynah-ui/src/components/icon/icons/undo.svg new file mode 100644 index 00000000..8788e7c8 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/undo.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/up-open.svg b/vendor/mynah-ui/src/components/icon/icons/up-open.svg new file mode 100644 index 00000000..fd5365cc --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/up-open.svg @@ -0,0 +1,7 @@ + + + + Layer 1 + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/icon/icons/user.svg b/vendor/mynah-ui/src/components/icon/icons/user.svg new file mode 100644 index 00000000..6e596720 --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/user.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/vendor/mynah-ui/src/components/icon/icons/warning.svg b/vendor/mynah-ui/src/components/icon/icons/warning.svg new file mode 100644 index 00000000..17b8f92a --- /dev/null +++ b/vendor/mynah-ui/src/components/icon/icons/warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/vendor/mynah-ui/src/components/more-content-indicator.ts b/vendor/mynah-ui/src/components/more-content-indicator.ts new file mode 100644 index 00000000..22110b58 --- /dev/null +++ b/vendor/mynah-ui/src/components/more-content-indicator.ts @@ -0,0 +1,57 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// eslint-disable @typescript-eslint/restrict-template-expressions +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { Icon, MynahIcons, MynahIconsType } from './icon'; +import { Button } from './button'; +import { StyleLoader } from '../helper/style-loader'; + +interface MoreContentIndicatorProps { + icon?: MynahIcons | MynahIconsType; + border?: boolean; + testId?: string; + onClick: () => void; +} +export class MoreContentIndicator { + render: ExtendedHTMLElement; + private props: MoreContentIndicatorProps; + private button: Button; + private readonly uid: string; + private readonly icon: ExtendedHTMLElement; + constructor (props: MoreContentIndicatorProps) { + StyleLoader.getInstance().load('components/_more-content-indicator.scss'); + this.props = props; + this.button = this.getButton(); + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'more-content-indicator' ], + testId: props.testId, + children: [ + this.button.render + ] + }); + } + + private readonly getButton = (): Button => { + return new Button({ + icon: new Icon({ icon: this.props.icon ?? MynahIcons.SCROLL_DOWN }).render, + primary: false, + fillState: 'hover', + border: this.props.border !== false, + onClick: this.props.onClick + }); + }; + + public update = (props: Partial): void => { + this.props = { + ...this.props, + ...props + }; + const newButton = this.getButton(); + this.button.render.replaceWith(newButton.render); + this.button = newButton; + }; +} diff --git a/vendor/mynah-ui/src/components/navigation-tab-bar-buttons.ts b/vendor/mynah-ui/src/components/navigation-tab-bar-buttons.ts new file mode 100644 index 00000000..3a8dd873 --- /dev/null +++ b/vendor/mynah-ui/src/components/navigation-tab-bar-buttons.ts @@ -0,0 +1,150 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { MynahUIGlobalEvents } from '../helper/events'; +import { MynahUITabsStore } from '../helper/tabs-store'; +import testIds from '../helper/test-ids'; +import { MynahEventNames, TabBarAction, TabBarMainAction } from '../static'; +import { Button } from './button'; +import { Icon } from './icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from './overlay'; + +export interface TabBarButtonsWrapperProps { + onButtonClick?: (selectedTabId: string, buttonId: string) => void; +} +export class TabBarButtonsWrapper { + render: ExtendedHTMLElement; + private selectedTabId: string; + private tabBarButtonsSubscription: {subsId: string | null; tabId: string} | null = null; + private readonly props: TabBarButtonsWrapperProps; + + constructor (props?: TabBarButtonsWrapperProps) { + this.props = props ?? {}; + this.selectedTabId = MynahUITabsStore.getInstance().getSelectedTabId(); + this.handleTabBarButtonsChange(); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.tabBar.buttonsWrapper, + persistent: true, + classNames: [ 'mynah-nav-tabs-bar-buttons-wrapper' ], + children: this.getTabsBarButtonsRender(this.selectedTabId), + }); + + MynahUITabsStore.getInstance().addListener('selectedTabChange', (selectedTabId) => { + this.selectedTabId = selectedTabId; + this.handleTabBarButtonsChange(); + this.render.clear(); + this.render.update({ + children: this.getTabsBarButtonsRender(selectedTabId) + }); + }); + } + + private readonly handleTabBarButtonsChange = (): void => { + if (this.tabBarButtonsSubscription?.subsId != null) { + MynahUITabsStore.getInstance().removeListenerFromDataStore(this.tabBarButtonsSubscription.tabId, this.tabBarButtonsSubscription.subsId, 'tabBarButtons'); + } + this.tabBarButtonsSubscription = { + subsId: MynahUITabsStore.getInstance().addListenerToDataStore(this.selectedTabId, 'tabBarButtons', (tabBarButtons) => { + this.render.clear(); + this.render.update({ + children: this.getTabsBarButtonsRender(this.selectedTabId, tabBarButtons) + }); + }), + tabId: this.selectedTabId + }; + }; + + private readonly getTabsBarButtonsRender = (selectedTabId: string, givenTabBarButtons?: TabBarMainAction[]): ExtendedHTMLElement[] => { + let tabBarButtons = Config.getInstance().config.tabBarButtons ?? []; + if (givenTabBarButtons != null) { + tabBarButtons = givenTabBarButtons; + } else { + const tabBarButtonsFromTabStore = MynahUITabsStore.getInstance().getTabDataStore(selectedTabId)?.getValue('tabBarButtons'); + if (tabBarButtonsFromTabStore != null && tabBarButtonsFromTabStore.length > 0) { + tabBarButtons = tabBarButtonsFromTabStore; + } + } + return tabBarButtons.map((tabBarButton: TabBarMainAction) => new TabBarButtonWithMultipleOptions({ + onButtonClick: (tabBarAction) => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.TAB_BAR_BUTTON_CLICK, { tabId: selectedTabId, buttonId: tabBarAction.id }); + if (this.props.onButtonClick != null) { + this.props.onButtonClick(selectedTabId, tabBarAction.id); + } + }, + tabBarActionButton: tabBarButton + }).render); + }; +} + +interface TabBarButtonWithMultipleOptionsProps { + onButtonClick: (action: TabBarAction) => void; + tabBarActionButton: TabBarMainAction; +} +export class TabBarButtonWithMultipleOptions { + render: ExtendedHTMLElement; + private buttonOptionsOverlay: Overlay | undefined; + private readonly props: TabBarButtonWithMultipleOptionsProps; + + constructor (props: TabBarButtonWithMultipleOptionsProps) { + this.props = props; + this.render = new Button({ + testId: (this.props.tabBarActionButton.items != null && this.props.tabBarActionButton.items?.length > 0) ? testIds.tabBar.menuButton : testIds.tabBar.button, + label: this.props.tabBarActionButton.text, + tooltip: this.props.tabBarActionButton.description, + // confirmation: this.props.tabBarActionButton.confirmation, + disabled: this.props.tabBarActionButton.disabled, + tooltipVerticalDirection: OverlayVerticalDirection.TO_BOTTOM, + tooltipHorizontalDirection: OverlayHorizontalDirection.CENTER, + icon: this.props.tabBarActionButton.icon != null ? new Icon({ icon: this.props.tabBarActionButton.icon }).render : undefined, + primary: false, + onClick: () => { + if (this.props.tabBarActionButton.items != null && this.props.tabBarActionButton.items?.length > 0) { + this.showButtonOptionsOverlay(this.props.tabBarActionButton.items); + } else { + this.props.onButtonClick(this.props.tabBarActionButton); + } + } + }).render; + } + + private readonly showButtonOptionsOverlay = (items: TabBarAction[]): void => { + this.buttonOptionsOverlay = new Overlay({ + background: true, + closeOnOutsideClick: true, + referenceElement: this.render, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_BOTTOM, + horizontalDirection: OverlayHorizontalDirection.END_TO_LEFT, + children: [ + { + type: 'div', + classNames: [ 'mynah-nav-tabs-bar-buttons-wrapper-overlay' ], + children: items.map(item => new Button({ + testId: testIds.tabBar.menuOption, + confirmation: item.confirmation, + label: item.text, + icon: item.icon != null ? new Icon({ icon: item.icon }).render : undefined, + primary: false, + onClick: () => { + this.hideButtonOptionsOverlay(); + this.props.onButtonClick(item); + } + }).render) + } + ], + }); + }; + + private readonly hideButtonOptionsOverlay = (): void => { + if (this.buttonOptionsOverlay !== undefined) { + this.buttonOptionsOverlay.close(); + this.buttonOptionsOverlay = undefined; + } + }; +} diff --git a/vendor/mynah-ui/src/components/navigation-tabs.ts b/vendor/mynah-ui/src/components/navigation-tabs.ts new file mode 100644 index 00000000..d02a5fbf --- /dev/null +++ b/vendor/mynah-ui/src/components/navigation-tabs.ts @@ -0,0 +1,258 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { cancelEvent } from '../helper/events'; +import { MynahUITabsStore } from '../helper/tabs-store'; +import { MynahUITabStoreTab } from '../static'; +import { Button } from './button'; +import { Card } from './card/card'; +import { CardBody } from './card/card-body'; +import { Icon, MynahIcons } from './icon'; +import { TabBarButtonsWrapper } from './navigation-tab-bar-buttons'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from './overlay'; +import { Tab, ToggleOption } from './tabs'; +import { DEFAULT_TIMEOUT } from './notification'; +import testIds from '../helper/test-ids'; +import { StyleLoader } from '../helper/style-loader'; + +export interface TabsProps { + onChange?: (selectedTabId: string) => void; + noMoreTabsTooltip?: string; + onBeforeTabRemove?: (tabId: string) => boolean; + maxTabsTooltipDuration?: number; +} +export class Tabs { + render: ExtendedHTMLElement; + private tabIdTitleSubscriptions: Record = {}; + private tabIdChatItemsSubscriptions: Record = {}; + private toggleGroup: Tab; + private maxReachedOverlay: Overlay | undefined; + private closeConfirmationOverlay: Overlay | undefined; + private readonly props: TabsProps; + + constructor (props: TabsProps) { + StyleLoader.getInstance().load('components/_nav-tabs.scss'); + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.tabBar.wrapper, + persistent: true, + classNames: [ 'mynah-nav-tabs-wrapper' ], + events: { + dblclick: (e) => { + cancelEvent(e); + if (MynahUITabsStore.getInstance().tabsLength() < Config.getInstance().config.maxTabs) { + MynahUITabsStore.getInstance().addTab(); + } + } + }, + children: [ + ...this.getTabsRender(MynahUITabsStore.getInstance().getSelectedTabId()), + new TabBarButtonsWrapper().render + ], + }); + + MynahUITabsStore.getInstance().addListener('add', (tabId, tabData) => { + this.assignListener(tabId); + this.toggleGroup.addOption({ + value: tabId, + label: tabData?.store?.tabTitle, + selected: tabData?.isSelected + }); + this.render.setAttribute('selected-tab', tabId); + }); + MynahUITabsStore.getInstance().addListener('remove', (tabId, newSelectedTab?: MynahUITabStoreTab) => { + this.removeListenerAssignments(tabId); + this.toggleGroup.removeOption(tabId); + if (newSelectedTab !== undefined) { + this.toggleGroup.snapToOption(MynahUITabsStore.getInstance().getSelectedTabId()); + } + this.render.setAttribute('selected-tab', MynahUITabsStore.getInstance().getSelectedTabId()); + }); + MynahUITabsStore.getInstance().addListener('selectedTabChange', (selectedTabId) => { + this.render.setAttribute('selected-tab', selectedTabId); + this.toggleGroup.setValue(selectedTabId); + }); + } + + private readonly getTabOptionsFromTabStoreData = (): ToggleOption[] => { + const tabs = MynahUITabsStore.getInstance().getAllTabs(); + return Object.keys(tabs).map((tabId: string) => { + const tabOption = { + value: tabId, + label: tabs[tabId].store?.tabTitle, + icon: tabs[tabId].store?.tabIcon, + pinned: tabs[tabId].store?.pinned, + selected: tabs[tabId].isSelected + }; + return tabOption; + }); + }; + + private readonly getTabsRender = (selectedTabId?: string): ExtendedHTMLElement[] => { + const tabs = this.getTabOptionsFromTabStoreData(); + tabs.forEach(tab => { + this.assignListener(tab.value); + }); + this.toggleGroup = new Tab({ + testId: testIds.tabBar.tabsWrapper, + onChange: (selectedTabId: string) => { + MynahUITabsStore.getInstance().selectTab(selectedTabId); + if (this.props.onChange !== undefined) { + this.props.onChange(selectedTabId); + } + }, + onRemove: (selectedTabId, domElement: ExtendedHTMLElement) => { + if (this.props.onBeforeTabRemove !== undefined && !this.props.onBeforeTabRemove(selectedTabId)) { + this.showCloseTabConfirmationOverLay(domElement, selectedTabId); + } else { + MynahUITabsStore.getInstance().removeTab(selectedTabId); + } + }, + name: 'mynah-main-tabs', + options: tabs, + value: selectedTabId + }); + return [ + this.toggleGroup.render, + new Button({ + testId: testIds.tabBar.tabAddButton, + classNames: [ 'mynah-tabs-close-button' ], + additionalEvents: { + mouseenter: (e) => { + if (MynahUITabsStore.getInstance().tabsLength() === Config.getInstance().config.maxTabs) { + this.showMaxReachedOverLay(e.currentTarget, this.props.noMoreTabsTooltip ?? Config.getInstance().config.texts.noMoreTabsTooltip, this.props.maxTabsTooltipDuration); + } + }, + mouseleave: () => { + this.hideMaxReachedOverLay(); + }, + }, + onClick: (e) => { + cancelEvent(e); + if (MynahUITabsStore.getInstance().tabsLength() < Config.getInstance().config.maxTabs) { + MynahUITabsStore.getInstance().addTab(); + } + }, + icon: new Icon({ icon: MynahIcons.PLUS }).render, + primary: false + }).render + ]; + }; + + private readonly showMaxReachedOverLay = (elm: HTMLElement, markdownText: string, duration?: number): void => { + this.maxReachedOverlay = new Overlay({ + testId: testIds.tabBar.maxTabsReachedOverlay, + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_BOTTOM, + horizontalDirection: OverlayHorizontalDirection.CENTER, + children: [ + new Card({ + border: false, + classNames: [ 'mynah-nav-tabs-max-reached-overlay' ], + children: [ + new CardBody({ + body: markdownText, + }).render, + ] + }).render + ], + }); + + if (duration !== undefined && duration !== -1) { + setTimeout(() => { + this.hideMaxReachedOverLay(); + }, duration); + } else if (duration === undefined) { + setTimeout(() => { + this.hideMaxReachedOverLay(); + }, DEFAULT_TIMEOUT); + } + }; + + private readonly hideMaxReachedOverLay = (): void => { + if (this.maxReachedOverlay !== undefined) { + this.maxReachedOverlay.close(); + this.maxReachedOverlay = undefined; + } + }; + + private readonly showCloseTabConfirmationOverLay = (elm: HTMLElement, selectedTabId: string): void => { + this.closeConfirmationOverlay = new Overlay({ + testId: testIds.tabBar.tabCloseConfirmationOverlay, + background: true, + closeOnOutsideClick: true, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_BOTTOM, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + new Card({ + border: false, + classNames: [ 'mynah-nav-tabs-close-confirmation-overlay' ], + children: [ + new CardBody({ + testId: testIds.tabBar.tabCloseConfirmationBody, + body: MynahUITabsStore.getInstance().getTabDataStore(selectedTabId).getValue('tabCloseConfirmationMessage') ?? + Config.getInstance().config.texts.tabCloseConfirmationMessage, + }).render, + DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-nav-tabs-close-confirmation-buttons-wrapper' ], + children: [ + new Button({ + testId: testIds.tabBar.tabCloseConfirmationCancelButton, + onClick: () => { + this.hideshowCloseTabConfirmationOverLay(); + }, + label: MynahUITabsStore.getInstance().getTabDataStore(selectedTabId).getValue('tabCloseConfirmationKeepButton') ?? + Config.getInstance().config.texts.tabCloseConfirmationKeepButton + }).render, + new Button({ + testId: testIds.tabBar.tabCloseConfirmationAcceptButton, + onClick: () => { + MynahUITabsStore.getInstance().removeTab(selectedTabId); + this.hideshowCloseTabConfirmationOverLay(); + }, + classNames: [ 'mynah-nav-tabs-close-confirmation-close-button' ], + label: MynahUITabsStore.getInstance().getTabDataStore(selectedTabId).getValue('tabCloseConfirmationCloseButton') ?? + Config.getInstance().config.texts.tabCloseConfirmationCloseButton + }).render, + ] + }) + ] + }).render + ], + }); + }; + + private readonly hideshowCloseTabConfirmationOverLay = (): void => { + if (this.closeConfirmationOverlay !== undefined) { + this.closeConfirmationOverlay.close(); + this.closeConfirmationOverlay = undefined; + } + }; + + private readonly assignListener = (tabId: string): void => { + this.tabIdTitleSubscriptions[tabId] = MynahUITabsStore.getInstance().addListenerToDataStore(tabId, 'tabTitle', (title) => { + this.toggleGroup.updateOptionTitle(tabId, title); + }) ?? ''; + this.tabIdChatItemsSubscriptions[tabId] = MynahUITabsStore.getInstance().addListenerToDataStore(tabId, 'chatItems', () => { + this.toggleGroup.updateOptionIndicator(tabId, true); + }) ?? ''; + }; + + private readonly removeListenerAssignments = (tabId: string): void => { + MynahUITabsStore.getInstance().removeListenerFromDataStore(tabId, this.tabIdTitleSubscriptions[tabId], 'tabTitle'); + MynahUITabsStore.getInstance().removeListenerFromDataStore(tabId, this.tabIdChatItemsSubscriptions[tabId], 'chatItems'); + }; +} diff --git a/vendor/mynah-ui/src/components/no-tabs.ts b/vendor/mynah-ui/src/components/no-tabs.ts new file mode 100644 index 00000000..cdbc19dd --- /dev/null +++ b/vendor/mynah-ui/src/components/no-tabs.ts @@ -0,0 +1,69 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Config } from '../helper/config'; +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { cancelEvent } from '../helper/events'; +import { MynahUITabsStore } from '../helper/tabs-store'; +import { Button } from './button'; +import { Icon, MynahIcons } from './icon'; +import testIds from '../helper/test-ids'; +import { parseMarkdown } from '../helper/marked'; +import { StyleLoader } from '../helper/style-loader'; + +export class NoTabs { + render: ExtendedHTMLElement; + constructor () { + StyleLoader.getInstance().load('components/_no-tabs.scss'); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.noTabs.wrapper, + persistent: true, + classNames: [ 'mynah-no-tabs-wrapper', ...(MynahUITabsStore.getInstance().tabsLength() > 0 ? [ 'hidden' ] : []) ], + children: [ + { + type: 'div', + classNames: [ 'mynah-no-tabs-icon-wrapper' ], + children: [ + new Icon({ icon: MynahIcons.TABS }).render + ] + }, + { + type: 'div', + classNames: [ 'mynah-no-tabs-info' ], + innerHTML: parseMarkdown(Config.getInstance().config.texts.noTabsOpen ?? '') + }, + { + type: 'div', + classNames: [ 'mynah-no-tabs-buttons-wrapper' ], + children: [ + new Button({ + testId: testIds.noTabs.newTabButton, + onClick: (e) => { + cancelEvent(e); + if (MynahUITabsStore.getInstance().tabsLength() < Config.getInstance().config.maxTabs) { + MynahUITabsStore.getInstance().addTab(); + } + }, + status: 'main', + icon: new Icon({ icon: MynahIcons.PLUS }).render, + label: Config.getInstance().config.texts.openNewTab + }).render + ] + } + ], + }); + + MynahUITabsStore.getInstance().addListener('add', () => { + this.render.addClass('hidden'); + }); + + MynahUITabsStore.getInstance().addListener('remove', () => { + if (MynahUITabsStore.getInstance().tabsLength() === 0) { + this.render.removeClass('hidden'); + } + }); + } +} diff --git a/vendor/mynah-ui/src/components/notification.ts b/vendor/mynah-ui/src/components/notification.ts new file mode 100644 index 00000000..2a46c495 --- /dev/null +++ b/vendor/mynah-ui/src/components/notification.ts @@ -0,0 +1,119 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilderObject, DS, ExtendedHTMLElement } from '../helper/dom'; +import { cancelEvent } from '../helper/events'; +import { NotificationType } from '../static'; +import { Icon, MynahIcons } from './icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection, OVERLAY_MARGIN } from './overlay'; +import testIds from '../helper/test-ids'; +import { StyleLoader } from '../helper/style-loader'; + +type NotificationContentType = string | ExtendedHTMLElement | HTMLElement | DomBuilderObject; + +export const DEFAULT_TIMEOUT = 5000; + +export interface NotificationProps { + duration?: number; + type?: NotificationType; + title?: string; + content: NotificationContentType | NotificationContentType[]; + onNotificationClick?: () => void; + onNotificationHide?: () => void; +} + +export class Notification { + private notificationOverlay!: Overlay; + private readonly duration; + private readonly type; + private readonly props; + + constructor (props: NotificationProps) { + StyleLoader.getInstance().load('components/_notification.scss'); + this.duration = props.duration ?? DEFAULT_TIMEOUT; + this.type = props.type ?? NotificationType.INFO; + this.props = props; + } + + public notify (): void { + this.notificationOverlay = new Overlay({ + referencePoint: { + left: Math.max(document.documentElement.clientWidth ?? 0, window.innerWidth ?? 0), + top: this.getNextCalculatedTop(), + }, + dimOutside: false, + closeOnOutsideClick: false, + horizontalDirection: OverlayHorizontalDirection.TO_LEFT, + verticalDirection: OverlayVerticalDirection.TO_BOTTOM, + onClose: this.props.onNotificationHide, + children: [ + { + type: 'div', + testId: testIds.notification.wrapper, + classNames: [ + 'mynah-notification', + this.props.onNotificationClick != null ? 'mynah-notification-clickable' : '', + ], + events: { + click: e => { + cancelEvent(e); + if (this.props.onNotificationClick != null) { + this.props.onNotificationClick(); + this.notificationOverlay?.close(); + } + }, + }, + children: [ + new Icon({ icon: this.type.toString() as MynahIcons }).render, + { + type: 'div', + classNames: [ 'mynah-notification-container' ], + children: [ + { + type: 'h3', + testId: testIds.notification.title, + classNames: [ 'mynah-notification-title' ], + children: [ this.props.title ?? '' ], + }, + { + type: 'div', + testId: testIds.notification.content, + classNames: [ 'mynah-notification-content' ], + children: this.getChildren(this.props.content), + }, + ], + }, + ], + }, + ], + }); + + if (this.duration !== -1) { + setTimeout(() => { + this.notificationOverlay?.close(); + }, this.duration); + } + } + + /** + * Calculates the top according to the previously shown and still visible notifications + * @returns number + */ + private readonly getNextCalculatedTop = (): number => { + const prevNotifications = DS('.mynah-notification'); + if (prevNotifications.length > 0) { + const prevNotificationRectangle = prevNotifications[prevNotifications.length - 1].getBoundingClientRect(); + return prevNotificationRectangle.top + prevNotificationRectangle.height + OVERLAY_MARGIN; + } + return 0; + }; + + private readonly getChildren = (content: NotificationContentType | NotificationContentType[]): NotificationContentType[] => { + if (content instanceof Array) { + return content; + } + return [ content ]; + }; +} diff --git a/vendor/mynah-ui/src/components/overlay.ts b/vendor/mynah-ui/src/components/overlay.ts new file mode 100644 index 00000000..ddc9e7b4 --- /dev/null +++ b/vendor/mynah-ui/src/components/overlay.ts @@ -0,0 +1,314 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +/* eslint-disable @typescript-eslint/brace-style */ +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../helper/dom'; +import { generateUID } from '../helper/guid'; +import { StyleLoader } from '../helper/style-loader'; +import { MynahPortalNames } from '../static'; + +export const OVERLAY_MARGIN = 8; +/** + * The horizontal creation direction of the overlay + */ +export enum OverlayHorizontalDirection { + /** + * starts from the left edge of the reference element and opens to left + */ + TO_LEFT = 'horizontal-direction-to-left', + /** + * starts from the right edge of the reference element and opens to left + */ + END_TO_LEFT = 'horizontal-direction-from-end-to-left', + /** + * starts from the right edge of the reference element and opens to right + */ + TO_RIGHT = 'horizontal-direction-to-right', + /** + * starts from the left edge of the reference element and opens to right + */ + START_TO_RIGHT = 'horizontal-direction-from-start-to-right', + /** + * starts and opens at the center of the reference element + */ + CENTER = 'horizontal-direction-at-center', +} + +/** + * The vertical creation direction of the overlay + */ +export enum OverlayVerticalDirection { + /** + * starts from the bottom edge of the reference element and opens to bottom + */ + TO_BOTTOM = 'vertical-direction-to-bottom', + /** + * starts from the top edge of the reference element and opens to bottom + */ + START_TO_BOTTOM = 'vertical-direction-from-start-to-bottom', + /** + * starts from the top edge of the reference element and opens to top + */ + TO_TOP = 'vertical-direction-to-top', + /** + * starts from the bottom edge of the reference element and opens to top + */ + END_TO_TOP = 'vertical-direction-from-end-to-top', + /** + * starts and opens at the center of the reference element + */ + CENTER = 'vertical-direction-at-center', +} + +export interface OverlayProps { + testId?: string; + referenceElement?: HTMLElement | ExtendedHTMLElement; + removeIfReferenceElementRemoved?: boolean; + referencePoint?: { top: number; left: number }; + children: Array; + horizontalDirection?: OverlayHorizontalDirection; + verticalDirection?: OverlayVerticalDirection; + stretchWidth?: boolean; + dimOutside?: boolean; + closeOnOutsideClick?: boolean; + background?: boolean; + onClose?: () => void; + removeOtherOverlays?: boolean; +} +export class Overlay { + render: ExtendedHTMLElement; + private readonly container: ExtendedHTMLElement; + private readonly innerContainer: ExtendedHTMLElement; + private readonly guid = generateUID(); + private readonly onClose; + + constructor (props: OverlayProps) { + StyleLoader.getInstance().load('components/_overlay.scss'); + const horizontalDirection = props.horizontalDirection ?? OverlayHorizontalDirection.TO_RIGHT; + const verticalDirection = props.verticalDirection ?? OverlayVerticalDirection.START_TO_BOTTOM; + this.onClose = props.onClose; + const dimOutside = props.dimOutside !== false; + const closeOnOutsideClick = props.closeOnOutsideClick !== false; + + const calculatedTop = this.getCalculatedTop(verticalDirection, props.referenceElement, props.referencePoint); + const calculatedLeft = this.getCalculatedLeft(horizontalDirection, props.referenceElement, props.referencePoint); + const calculatedWidth = props.stretchWidth === true ? this.getCalculatedWidth(props.referenceElement) : 0; + + this.innerContainer = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-overlay-inner-container' ], + children: props.children, + }); + + this.container = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-overlay-container', horizontalDirection, verticalDirection, props.background !== false ? 'background' : '' ], + attributes: { + style: `top: ${calculatedTop}px; left: ${calculatedLeft}px; ${calculatedWidth !== 0 ? `width: ${calculatedWidth}px;` : ''}`, + }, + children: [ this.innerContainer ], + }); + + if (props.removeOtherOverlays === true) { + DomBuilder.getInstance().removeAllPortals(MynahPortalNames.OVERLAY); + } + + // this is a portal that goes over all the other items + // to make it as an overlay item + this.render = DomBuilder.getInstance().createPortal( + `${MynahPortalNames.OVERLAY}-${this.guid}`, + { + type: 'div', + testId: props.testId, + attributes: { id: `mynah-overlay-${this.guid}` }, + classNames: [ + 'mynah-overlay', + ...(dimOutside ? [ 'mynah-overlay-dim-outside' ] : []), + ...(closeOnOutsideClick ? [ 'mynah-overlay-close-on-outside-click' ] : []), + ], + events: { + click: closeOnOutsideClick + ? (event: MouseEvent) => { + // Only close if the click is outside the overlay + if (event.target === event.currentTarget) { + this.close(); + } + } + : () => {}, + }, + children: [ this.container ], + }, + 'beforeend' + ); + + // Screen edge fixes + const winHeight = Math.max(document.documentElement.clientHeight ?? 0, window.innerHeight ?? 0); + const winWidth = Math.max(document.documentElement.clientWidth ?? 0, window.innerWidth ?? 0); + const lastContainerRect = this.container.getBoundingClientRect(); + const effectiveTop = parseFloat(this.container.style.top ?? '0'); + const effectiveLeft = parseFloat(this.container.style.left ?? '0'); + + // Vertical edge + // Check top exceeding + if (lastContainerRect.top < OVERLAY_MARGIN) { + this.container.style.top = `${effectiveTop + (OVERLAY_MARGIN - lastContainerRect.top)}px`; + } // Check bottom exceeding + else if (lastContainerRect.top + lastContainerRect.height + OVERLAY_MARGIN > winHeight) { + this.container.style.top = `${effectiveTop - (lastContainerRect.top + lastContainerRect.height + OVERLAY_MARGIN - winHeight)}px`; + } + + // Horizontal edge + // Check left exceeding + if (lastContainerRect.left < OVERLAY_MARGIN) { + this.container.style.left = `${effectiveLeft + (OVERLAY_MARGIN - lastContainerRect.left)}px`; + } // Check right exceeding + else if (lastContainerRect.left + lastContainerRect.width + OVERLAY_MARGIN > winWidth) { + this.container.style.left = `${effectiveLeft - (lastContainerRect.left + lastContainerRect.width + OVERLAY_MARGIN - winWidth)}px`; + } + + this.preventTransformBlur(); + + // Check if reference element is still on dom tree + if (MutationObserver != null && props.removeIfReferenceElementRemoved !== false && props.referenceElement != null) { + const observer = new MutationObserver(() => { + if (!document.contains(props.referenceElement as HTMLElement)) { + this.close(); + observer.disconnect(); + } + }); + + // Observe the document body for any subtree modifications + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + // we need to delay the class toggle + // to avoid the skipping of the transition comes from css + // for a known js-css relation problem + setTimeout(() => { + this.render.addClass('mynah-overlay-open'); + + if (closeOnOutsideClick) { + window.addEventListener('blur', this.windowBlurHandler.bind(this)); + window.addEventListener('resize', this.windowBlurHandler.bind(this)); + } + }, 10); + } + + /** + * Applying a transform with a fractional pixel value causes bluriness on certain displays. + * + * Since transform uses --overlayTopPos which is a percentage of the overlay's height, and the height can be a fractional + * pixel value if line-height is fractional, this function rounds --overlayTopPos to an integer pixel value to prevent bluriness. + */ + private readonly preventTransformBlur = (): void => { + if (ResizeObserver != null) { + const observer = new ResizeObserver(() => { + const lastContainerRect = this.container.getBoundingClientRect(); + const height = lastContainerRect.height; + + const style = getComputedStyle(this.container); + const shiftPercent = parseFloat(style.getPropertyValue('--overlayTopPos')); + + const shiftPixels = Math.round(height * shiftPercent / 100); + + this.container.style.transform = `translate3d(var(--overlayLeftPos), ${shiftPixels}px, 0)`; + }); + + observer.observe(this.container); + } + }; + + close = (): void => { + this.render.removeClass('mynah-overlay-open'); + // In this timeout, we're waiting the close animation to be ended + setTimeout(() => { + this.render.remove(); + }, 250); + if (this.onClose !== undefined) { + this.onClose(); + } + }; + + private readonly windowBlurHandler = (): void => { + this.close(); + window.removeEventListener('blur', this.windowBlurHandler.bind(this)); + window.removeEventListener('resize', this.windowBlurHandler.bind(this)); + }; + + private readonly getCalculatedLeft = ( + horizontalDirection: OverlayHorizontalDirection, + referenceElement?: HTMLElement | ExtendedHTMLElement, + referencePoint?: { top?: number; left: number } + ): number => { + const referenceRectangle = + referenceElement !== undefined + ? referenceElement.getBoundingClientRect() + : referencePoint !== undefined + ? { left: referencePoint.left, width: 0 } + : { left: 0, width: 0 }; + + switch (horizontalDirection.toString()) { + case OverlayHorizontalDirection.TO_RIGHT: + return referenceRectangle.left + referenceRectangle.width + OVERLAY_MARGIN; + case OverlayHorizontalDirection.START_TO_RIGHT: + return referenceRectangle.left; + case OverlayHorizontalDirection.TO_LEFT: + return referenceRectangle.left - OVERLAY_MARGIN; + case OverlayHorizontalDirection.END_TO_LEFT: + return referenceRectangle.left + referenceRectangle.width; + case OverlayHorizontalDirection.CENTER: + return referenceRectangle.left + referenceRectangle.width / 2; + default: + return 0; + } + }; + + private readonly getCalculatedWidth = ( + referenceElement?: HTMLElement | ExtendedHTMLElement + ): number => { + return referenceElement !== undefined + ? referenceElement.getBoundingClientRect().width + : 0; + }; + + private readonly getCalculatedTop = ( + verticalDirection: OverlayVerticalDirection, + referenceElement?: HTMLElement | ExtendedHTMLElement, + referencePoint?: { top: number; left?: number } + ): number => { + const referenceRectangle = + referenceElement !== undefined + ? referenceElement.getBoundingClientRect() + : referencePoint !== undefined + ? { top: referencePoint.top, height: 0 } + : { top: 0, height: 0 }; + + switch (verticalDirection.toString()) { + case OverlayVerticalDirection.TO_BOTTOM: + return referenceRectangle.top + referenceRectangle.height + OVERLAY_MARGIN; + case OverlayVerticalDirection.START_TO_BOTTOM: + return referenceRectangle.top; + case OverlayVerticalDirection.TO_TOP: + return referenceRectangle.top - OVERLAY_MARGIN; + case OverlayVerticalDirection.END_TO_TOP: + return referenceRectangle.top + referenceRectangle.height; + case OverlayVerticalDirection.CENTER: + return referenceRectangle.top + referenceRectangle.height / 2; + default: + return referenceRectangle.top; + } + }; + + public updateContent = (children: Array): void => { + this.innerContainer.update({ children }); + }; + + public toggleHidden = (hidden: boolean): void => { + this.render.hidden = hidden; + }; +} diff --git a/vendor/mynah-ui/src/components/progress.ts b/vendor/mynah-ui/src/components/progress.ts new file mode 100644 index 00000000..8cf6f4f7 --- /dev/null +++ b/vendor/mynah-ui/src/components/progress.ts @@ -0,0 +1,116 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// eslint-disable @typescript-eslint/restrict-template-expressions +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { ChatItemButton, ProgressField } from '../static'; +import { ChatItemButtonsWrapper } from './chat-item/chat-item-buttons'; +import { StyleLoader } from '../helper/style-loader'; + +interface ProgressIndicatorProps extends ProgressField{ + testId?: string; + classNames?: string[]; + onClick?: () => void; + onActionClick?: (actionName: ChatItemButton, e?: Event) => void; +} +export class ProgressIndicator { + render: ExtendedHTMLElement; + private readonly wrapper: ExtendedHTMLElement; + private readonly text: ExtendedHTMLElement; + private readonly valueText: ExtendedHTMLElement; + private readonly valueBar: ExtendedHTMLElement; + private buttonsWrapper: ChatItemButtonsWrapper; + private props: ProgressIndicatorProps; + constructor (props: ProgressIndicatorProps) { + StyleLoader.getInstance().load('components/_progress.scss'); + this.props = props; + this.wrapper = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-progress-indicator-wrapper' ], + }); + this.text = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-progress-indicator-text' ] + }); + this.valueText = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-progress-indicator-value-text' ] + }); + this.valueBar = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-progress-indicator-value-bar' ] + }); + this.buttonsWrapper = this.getButtonsWrapper(); + this.wrapper.update({ + children: [ + this.valueBar, + this.text, + this.valueText, + this.buttonsWrapper.render + ] + }); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: this.props.testId, + classNames: [ 'mynah-progress-indicator', ...(this.props.classNames ?? []), ...(this.isEmpty() ? [ 'no-content' ] : []) ], + children: [ this.wrapper ], + }); + + this.update(props); + } + + private readonly getButtonsWrapper = (): ChatItemButtonsWrapper => { + const newButtons = new ChatItemButtonsWrapper({ + buttons: this.props.actions ?? [], + onActionClick: this.props.onActionClick ?? ((action) => {}), + }); + if (this.buttonsWrapper != null) { + this.buttonsWrapper.render.replaceWith(newButtons.render); + } + return newButtons; + }; + + public isEmpty = (): boolean => this.props.actions == null && this.props.text == null && this.props.valueText == null; + + public update = (props: Partial | null): void => { + if (props === null) { + this.props.actions = undefined; + this.props.status = undefined; + this.props.text = undefined; + this.props.value = undefined; + this.props.valueText = undefined; + } + this.props = { + ...this.props, + ...props + }; + this.valueBar.update({ + attributes: { + style: `width: ${this.props.value === -1 ? 100 : Math.min(100, this.props.value ?? 0)}%;` + } + }); + this.text.update({ + children: [ this.props.text ?? '' ] + }); + this.valueText.update({ + children: [ this.props.valueText ?? '' ] + }); + if ((props?.actions) !== undefined) { + this.buttonsWrapper = this.getButtonsWrapper(); + } + this.wrapper.update({ + attributes: { + ...(this.props.value === -1 ? { indeterminate: 'true' } : {}), + 'progress-status': this.props.status ?? 'default' + } + }); + if (this.isEmpty()) { + this.render?.addClass('no-content'); + } else { + this.render?.removeClass('no-content'); + } + }; +} diff --git a/vendor/mynah-ui/src/components/sheet.ts b/vendor/mynah-ui/src/components/sheet.ts new file mode 100644 index 00000000..3a247945 --- /dev/null +++ b/vendor/mynah-ui/src/components/sheet.ts @@ -0,0 +1,225 @@ +/*! + * Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import testIds from '../helper/test-ids'; +import { ChatItemButton, DomBuilder, DomBuilderObject, ExtendedHTMLElement, MynahEventNames, MynahIcons, MynahPortalNames, Status, TabBarAction, TabBarMainAction } from '../main'; +import { cancelEvent, MynahUIGlobalEvents } from '../helper/events'; +import { Button } from './button'; +import { Icon, MynahIconsType } from './icon'; +import { CardBody } from './card/card-body'; +import { StyleLoader } from '../helper/style-loader'; +import { TabBarButtonWithMultipleOptions } from './navigation-tab-bar-buttons'; +import { Card } from './card/card'; +import { TitleDescriptionWithIcon } from './title-description-with-icon'; +import { parseMarkdown } from '../helper/marked'; + +export interface SheetProps { + title?: string; + children?: Array; + fullScreen?: boolean; + showBackButton?: boolean; + description?: string; + status?: { + icon?: MynahIcons | MynahIconsType; + title?: string; + description?: string; + status?: Status; + }; + actions?: TabBarAction[]; + onClose: () => void; + onBack: () => void; + onActionClick?: (action: TabBarAction) => void; +} + +export class Sheet { + private backButton: Button; + private sheetTitle: ExtendedHTMLElement; + private sheetTitleActions: ExtendedHTMLElement; + private sheetStatus: ExtendedHTMLElement; + private sheetDescription: ExtendedHTMLElement; + sheetContainer: ExtendedHTMLElement; + sheetWrapper: ExtendedHTMLElement; + onClose: () => void; + onBack: () => void; + onActionClick: ((action: ChatItemButton) => void) | undefined; + + constructor () { + StyleLoader.getInstance().load('components/_sheet.scss'); + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.OPEN_SHEET, (data: SheetProps) => { + if (this.sheetWrapper === undefined) { + this.sheetWrapper = DomBuilder.getInstance().createPortal( + MynahPortalNames.SHEET, + { + type: 'div', + testId: testIds.sheet.wrapper, + attributes: { + id: 'mynah-sheet-wrapper' + }, + }, + 'afterbegin' + ); + } + + this.sheetWrapper.clear(); + this.onClose = data.onClose; + this.onBack = data.onBack; + this.onActionClick = data.onActionClick; + this.backButton = new Button({ + icon: new Icon({ icon: 'left-open' }).render, + status: 'clear', + classNames: [ 'mynah-sheet-back-button' ], + primary: false, + border: false, + hidden: data.showBackButton !== true, + onClick: this.onBack + }); + this.sheetTitle = this.getTitle(data.title); + this.sheetDescription = this.getDescription(data.description); + this.sheetStatus = this.getStatus(data.status); + this.sheetTitleActions = this.getTitleActions(data.actions); + + this.sheetWrapper.update({ + children: [ + DomBuilder.getInstance().build( + { + type: 'div', + classNames: [ 'mynah-sheet', data.fullScreen === true ? 'mynah-sheet-fullscreen' : '' ], + events: { + click: (e) => { + if (e.target != null && !(e.target as HTMLElement).classList.contains('mynah-ui-clickable-item')) { + cancelEvent(e); + } + } + }, + children: [ + { + type: 'div', + classNames: [ 'mynah-sheet-header' ], + children: [ + this.backButton.render, + this.sheetTitle, + this.sheetTitleActions, + new Button({ + testId: testIds.sheet.closeButton, + primary: false, + onClick: (e) => { + cancelEvent(e); + this.close(); + }, + icon: new Icon({ icon: MynahIcons.CANCEL }).render + }).render + ] + }, + { + type: 'div', + classNames: [ 'mynah-sheet-body' ], + children: [ + this.sheetStatus, + this.sheetDescription, + ...(data.children ?? []) + ] + }, + ], + }) + ] + }); + + setTimeout(() => { + this.show(); + }, 5); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CLOSE_SHEET, () => { + this.close(); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.UPDATE_SHEET, (data: SheetProps) => { + if (data.showBackButton != null) { + this.backButton.setHidden(!data.showBackButton); + } + if (data.title != null) { + const newTitle = this.getTitle(data.title); + this.sheetTitle.replaceWith(newTitle); + this.sheetTitle = newTitle; + } + if (data.status != null) { + const newStatus = this.getStatus(data.status); + this.sheetStatus.replaceWith(newStatus); + this.sheetStatus = newStatus; + } + if (data.description != null) { + const newDescription = this.getDescription(data.description); + this.sheetDescription.replaceWith(newDescription); + this.sheetDescription = newDescription; + } + if (data.actions != null) { + const newActions = this.getTitleActions(data.actions); + this.sheetTitleActions.replaceWith(newActions); + this.sheetTitleActions = newActions; + } + }); + } + + private readonly getTitle = (title?: string): ExtendedHTMLElement => { + return DomBuilder.getInstance().build({ + type: 'h4', + testId: testIds.sheet.title, + children: [ title ?? '' ], + }); + }; + + private readonly getTitleActions = (actions?: ChatItemButton[]): ExtendedHTMLElement => { + return DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.sheet.title, + classNames: [ 'mynah-sheet-header-actions-container' ], + children: actions?.map((actionItem: TabBarMainAction) => { + return new TabBarButtonWithMultipleOptions({ + onButtonClick: (tabBarAction) => { + this.onActionClick?.(tabBarAction); + }, + tabBarActionButton: actionItem + }).render; + }) + }); + }; + + private readonly getDescription = (description?: string): ExtendedHTMLElement => new CardBody({ + testId: testIds.sheet.description, + body: description ?? '', + }).render; + + private readonly getStatus = (status?: { + icon?: MynahIcons | MynahIconsType; + title?: string; + description?: string; + status?: Status; + }): ExtendedHTMLElement => status?.title != null || status?.description != null + ? new Card({ + testId: testIds.sheet.description, + border: true, + padding: 'medium', + classNames: [ 'mynah-sheet-header-status' ], + status: status?.status, + children: [ + ...(status.title != null + ? [ new TitleDescriptionWithIcon({ + title: status?.title != null ? DomBuilder.getInstance().build({ classNames: [ 'mynah-sheet-header-status-title' ], type: 'div', innerHTML: parseMarkdown(status.title, { includeLineBreaks: false }) }) : undefined, + icon: status?.icon + }).render ] + : []), + ...(status.description != null ? [ new CardBody({ body: status.description }).render ] : []) ], + }).render + : DomBuilder.getInstance().build({ type: 'span' }); + + close = (): void => { + this.sheetWrapper.removeClass('mynah-sheet-show'); + this.onClose?.(); + }; + + show = (): void => { + this.sheetWrapper.addClass('mynah-sheet-show'); + }; +} diff --git a/vendor/mynah-ui/src/components/source-link/source-link-body.ts b/vendor/mynah-ui/src/components/source-link/source-link-body.ts new file mode 100644 index 00000000..68f6b3f5 --- /dev/null +++ b/vendor/mynah-ui/src/components/source-link/source-link-body.ts @@ -0,0 +1,29 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { + ReferenceTrackerInformation, + SourceLink, +} from '../../static'; +import { CardBody } from '../card/card-body'; + +export interface SourceLinkBodyProps { + suggestion: Partial; + children?: Array; + highlightRangeWithTooltip?: ReferenceTrackerInformation[]; +} +export class SourceLinkBody { + render: ExtendedHTMLElement; + props: SourceLinkBodyProps; + constructor (props: SourceLinkBodyProps) { + this.props = props; + this.render = new CardBody({ + highlightRangeWithTooltip: props.highlightRangeWithTooltip, + body: this.props.suggestion.body ?? '', + children: this.props.children, + }).render; + } +} diff --git a/vendor/mynah-ui/src/components/source-link/source-link-header.ts b/vendor/mynah-ui/src/components/source-link/source-link-header.ts new file mode 100644 index 00000000..4e270e60 --- /dev/null +++ b/vendor/mynah-ui/src/components/source-link/source-link-header.ts @@ -0,0 +1,232 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getTimeDiff } from '../../helper/date-time'; +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../../helper/dom'; +import { MynahUIGlobalEvents } from '../../helper/events'; +import testIds from '../../helper/test-ids'; +import { getOrigin } from '../../helper/url'; +import { MynahEventNames, SourceLink, SourceLinkMetaData } from '../../static'; +import { Icon, MynahIcons } from '../icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from '../overlay'; +import { SourceLinkCard } from './source-link'; + +const PREVIEW_DELAY = 500; +export interface SourceLinkHeaderProps { + sourceLink: SourceLink; + showCardOnHover?: boolean; + onClick?: (e?: MouseEvent) => void; +} +export class SourceLinkHeader { + private sourceLinkPreview: Overlay | null; + private sourceLinkPreviewTimeout: ReturnType; + render: ExtendedHTMLElement; + constructor (props: SourceLinkHeaderProps) { + const splitUrl = props.sourceLink.url + .replace(/^(http|https):\/\//, '') + .split('/'); + if (splitUrl[splitUrl.length - 1].trim() === '') { + splitUrl.pop(); + } + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.ROOT_FOCUS, (data: {focusState: boolean}) => { + if (!data.focusState) { + this.hideLinkPreview(); + } + }); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.relatedLinks.linkWrapper, + classNames: [ 'mynah-source-link-header' ], + ...(props.showCardOnHover === true + ? { + events: { + mouseenter: (e) => { + this.showLinkPreview(e, props.sourceLink); + }, + mouseleave: this.hideLinkPreview, + focus: (e) => { + this.showLinkPreview(e, props.sourceLink); + }, + blur: this.hideLinkPreview, + } + } + : {}), + attributes: { + origin: getOrigin(props.sourceLink.url) + }, + children: [ + { + type: 'span', + classNames: [ 'mynah-source-thumbnail' ] + }, + { + type: 'div', + classNames: [ 'mynah-source-link-title-wrapper' ], + children: [ + { + type: 'a', + classNames: [ 'mynah-source-link-title' ], + events: { + ...(props.onClick !== undefined && { + click: props.onClick, + auxclick: props.onClick, + }), + }, + attributes: { href: props.sourceLink.url, target: '_blank' }, + children: [ props.sourceLink.title, { + type: 'div', + classNames: [ 'mynah-source-link-expand-icon' ], + children: [ new Icon({ icon: MynahIcons.EXTERNAL }).render ], + } ], + }, + { + type: 'a', + testId: testIds.chatItem.relatedLinks.link, + classNames: [ 'mynah-source-link-url' ], + events: { + ...(props.onClick !== undefined && { + click: props.onClick, + auxclick: props.onClick + }), + }, + attributes: { href: props.sourceLink.url, target: '_blank' }, + innerHTML: splitUrl.map(urlPart => `${urlPart}`).join(''), + }, + ...((props.sourceLink.metadata != null) ? [ this.getSourceMetaBlock(props.sourceLink.metadata) ] : []), + ], + }, + ], + }); + } + + private readonly getSourceMetaBlock = (metadataUnion?: Record): DomBuilderObject => { + const metaItems: any[] = []; + if (metadataUnion !== null && metadataUnion !== undefined) { + Object.keys(metadataUnion).forEach(metadataKey => { + const metadata = metadataUnion[metadataKey]; + if (metadata.isAccepted === true) { + metaItems.push({ + type: 'span', + classNames: [ 'mynah-title-meta-block-item', 'approved-answer' ], + children: [ + new Icon({ icon: MynahIcons.OK }).render, + ] + }); + } + + if (metadata.lastActivityDate !== undefined) { + metaItems.push({ + type: 'span', + classNames: [ 'mynah-title-meta-block-item' ], + children: [ + new Icon({ icon: MynahIcons.CALENDAR }).render, + { + type: 'span', + classNames: [ 'mynah-title-meta-block-item-text' ], + children: [ getTimeDiff((new Date()).getTime() - metadata.lastActivityDate, 2) ] + } + ] + }); + } + + if (metadata.answerCount !== undefined) { + metaItems.push({ + type: 'span', + classNames: [ 'mynah-title-meta-block-item' ], + children: [ + new Icon({ icon: MynahIcons.CHAT }).render, + { + type: 'span', + classNames: [ 'mynah-title-meta-block-item-text' ], + children: [ metadata.answerCount.toString() ] + } + ] + }); + } + + if (metadata.stars !== undefined) { + metaItems.push({ + type: 'span', + classNames: [ 'mynah-title-meta-block-item' ], + children: [ + new Icon({ icon: MynahIcons.STAR }).render, + { + type: 'span', + classNames: [ 'mynah-title-meta-block-item-text' ], + children: [ `${metadata.stars.toString()} contributors` ] + } + ] + }); + } + + if (metadata.forks !== undefined) { + metaItems.push({ + type: 'span', + classNames: [ 'mynah-title-meta-block-item' ], + children: [ + new Icon({ icon: MynahIcons.DOWN_OPEN }).render, + { + type: 'span', + classNames: [ 'mynah-title-meta-block-item-text' ], + children: [ `${metadata.forks.toString()} forks` ] + } + ] + }); + } + + if (metadata.score !== undefined) { + metaItems.push({ + type: 'span', + classNames: [ 'mynah-title-meta-block-item' ], + children: [ + new Icon({ icon: MynahIcons.THUMBS_UP }).render, + { + type: 'span', + classNames: [ 'mynah-title-meta-block-item-text' ], + children: [ `${metadata.score.toString()}` ] + } + ] + }); + } + }); + } + + return { + type: 'span', + classNames: [ 'mynah-title-meta-block' ], + children: metaItems + }; + }; + + private readonly showLinkPreview = (e: MouseEvent, sourceLink: SourceLink): void => { + if (sourceLink.body !== undefined) { + clearTimeout(this.sourceLinkPreviewTimeout); + this.sourceLinkPreviewTimeout = setTimeout(() => { + const elm: HTMLElement = e.target as HTMLElement; + this.sourceLinkPreview = new Overlay({ + testId: testIds.chatItem.relatedLinks.linkPreviewOverlay, + background: true, + closeOnOutsideClick: false, + referenceElement: elm, + dimOutside: false, + removeOtherOverlays: true, + verticalDirection: OverlayVerticalDirection.TO_TOP, + horizontalDirection: OverlayHorizontalDirection.START_TO_RIGHT, + children: [ + new SourceLinkCard({ sourceLink }).render + ], + }); + }, PREVIEW_DELAY); + } + }; + + private readonly hideLinkPreview = (): void => { + clearTimeout(this.sourceLinkPreviewTimeout); + if (this.sourceLinkPreview !== null) { + this.sourceLinkPreview?.close(); + this.sourceLinkPreview = null; + } + }; +} diff --git a/vendor/mynah-ui/src/components/source-link/source-link.ts b/vendor/mynah-ui/src/components/source-link/source-link.ts new file mode 100644 index 00000000..59a2d54e --- /dev/null +++ b/vendor/mynah-ui/src/components/source-link/source-link.ts @@ -0,0 +1,31 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ExtendedHTMLElement } from '../../helper/dom'; +import testIds from '../../helper/test-ids'; +import { SourceLink } from '../../static'; +import { Card } from '../card/card'; +import { SourceLinkBody } from './source-link-body'; +import { SourceLinkHeader } from './source-link-header'; + +export interface SourceLinkCardProps {sourceLink: SourceLink; compact?: 'flat' | true} +export class SourceLinkCard { + private readonly sourceLink: SourceLink; + render: ExtendedHTMLElement; + constructor (props: SourceLinkCardProps) { + this.sourceLink = props.sourceLink; + this.render = new Card({ + testId: testIds.chatItem.relatedLinks.linkPreviewOverlayCard, + border: false, + background: false, + children: [ + new SourceLinkHeader({ + sourceLink: this.sourceLink + }).render, + ...(this.sourceLink.body !== undefined ? [ new SourceLinkBody({ suggestion: this.sourceLink }).render ] : []), + ], + }).render; + } +} diff --git a/vendor/mynah-ui/src/components/spinner/logo-base.svg b/vendor/mynah-ui/src/components/spinner/logo-base.svg new file mode 100644 index 00000000..c06f1b87 --- /dev/null +++ b/vendor/mynah-ui/src/components/spinner/logo-base.svg @@ -0,0 +1,13 @@ + + + + + + + + Layer 1 + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/spinner/logo-text.svg b/vendor/mynah-ui/src/components/spinner/logo-text.svg new file mode 100644 index 00000000..6fd3e561 --- /dev/null +++ b/vendor/mynah-ui/src/components/spinner/logo-text.svg @@ -0,0 +1,13 @@ + + + + + + + + Layer 1 + + + + + \ No newline at end of file diff --git a/vendor/mynah-ui/src/components/spinner/spinner.ts b/vendor/mynah-ui/src/components/spinner/spinner.ts new file mode 100644 index 00000000..110a3fa1 --- /dev/null +++ b/vendor/mynah-ui/src/components/spinner/spinner.ts @@ -0,0 +1,76 @@ +/* eslint-disable @typescript-eslint/no-extraneous-class */ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../../helper/dom'; +import { StyleLoader } from '../../helper/style-loader'; +import LOGO_BASE from './logo-base.svg'; +import LOGO_TEXT from './logo-text.svg'; + +export class Spinner { + render: ExtendedHTMLElement; + constructor () { + StyleLoader.getInstance().load('components/_spinner.scss'); + const portal = DomBuilder.getInstance().getPortal('mynah-ui-icons') ?? DomBuilder.getInstance().createPortal('mynah-ui-icons', { + type: 'style', + attributes: { + type: 'text/css' + } + }, 'beforebegin'); + portal.insertAdjacentText('beforeend', ` + :root{ + --mynah-ui-spinner-base: url(${LOGO_BASE}); + --mynah-ui-spinner-text: url(${LOGO_TEXT}); + } + `); + + this.render = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-ui-spinner-container' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-part', 'backdrop' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-mask', 'base' ] + } + ] + }, + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-part', 'semi-backdrop' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-mask', 'base' ] + } + ] + }, + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-part' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-mask', 'base' ] + } + ] + }, + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-part' ], + children: [ + { + type: 'span', + classNames: [ 'mynah-ui-spinner-logo-mask', 'text' ] + } + ] + } + ] + }); + } +} diff --git a/vendor/mynah-ui/src/components/syntax-highlighter.ts b/vendor/mynah-ui/src/components/syntax-highlighter.ts new file mode 100644 index 00000000..a686cc22 --- /dev/null +++ b/vendor/mynah-ui/src/components/syntax-highlighter.ts @@ -0,0 +1,245 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; + +import { + CodeBlockActions, + CodeSelectionType, + OnCodeBlockActionFunction, +} from '../static'; +import { Button } from './button'; +import { Icon, MynahIcons } from './icon'; +import { cancelEvent } from '../helper/events'; +import { highlightersWithTooltip } from './card/card-body'; +import escapeHTML from 'escape-html'; +import { copyToClipboard } from '../helper/chat-item'; +import testIds from '../helper/test-ids'; +import unescapeHTML from 'unescape-html'; +import hljs from 'highlight.js'; +import { mergeHTMLPlugin } from '../helper/merge-html-plugin'; +import { MoreContentIndicator } from './more-content-indicator'; +import { StyleLoader } from '../helper/style-loader'; + +export interface SyntaxHighlighterProps { + codeStringWithMarkup: string; + language?: string; + showLineNumbers?: boolean; + block?: boolean; + wrapCodeBlock?: boolean; + startingLineNumber?: number; + index?: number; + codeBlockActions?: CodeBlockActions; + hideLanguage?: boolean; + unlimitedHeight?: boolean; + onCopiedToClipboard?: (type?: CodeSelectionType, text?: string, codeBlockIndex?: number) => void; + onCodeBlockAction?: OnCodeBlockActionFunction; +} + +const DEFAULT_LANGUAGE = 'c'; + +export class SyntaxHighlighter { + private readonly props: SyntaxHighlighterProps; + private readonly codeBlockButtons: ExtendedHTMLElement[] = []; + render: ExtendedHTMLElement; + + constructor (props: SyntaxHighlighterProps) { + StyleLoader.getInstance().load('components/_syntax-highlighter.scss'); + this.props = props; + + hljs.addPlugin(mergeHTMLPlugin); + hljs.configure({ ignoreUnescapedHTML: true }); + + // To ensure we are not leaving anything unescaped before escaping i.e to prevent double escaping + let escapedCodeBlock = escapeHTML(unescapeHTML(props.codeStringWithMarkup)); + + // Convert reference tracker escaped markups back to original incoming from the parent + escapedCodeBlock = escapedCodeBlock + .replace(new RegExp(escapeHTML(highlightersWithTooltip.start.markupStart), 'g'), highlightersWithTooltip.start.markupStart) + .replace(new RegExp(escapeHTML(highlightersWithTooltip.start.markupEnd), 'g'), highlightersWithTooltip.start.markupEnd) + .replace(new RegExp(escapeHTML(highlightersWithTooltip.end.markup), 'g'), highlightersWithTooltip.end.markup); + + const codeElement = DomBuilder.getInstance().build({ + type: 'code', + classNames: [ + ...(props.language != null ? [ `language-${props.language.replace('diff-', '')}` ] : [ (props.block ?? false) ? DEFAULT_LANGUAGE : 'language-plaintext' ]), + ...(props.showLineNumbers === true ? [ 'line-numbers' ] : []), + ], + innerHTML: escapedCodeBlock + }); + hljs.highlightElement(codeElement); + + // Overlay another code element for diffs, as highlight.js doesn't allow multiple language styles + const diffOverlay = DomBuilder.getInstance().build({ + type: 'code', + classNames: [ 'diff', 'language-diff' ], + innerHTML: escapedCodeBlock + }); + hljs.highlightElement(diffOverlay); + + const preElement = DomBuilder.getInstance().build({ + type: 'pre', + testId: testIds.chatItem.syntaxHighlighter.codeBlock, + children: [ + codeElement, + ((props.language?.match('diff')) != null) ? diffOverlay : '' + ], + events: { + copy: (e) => { + cancelEvent(e); + const selectedCode = this.getSelectedCodeContextMenu(); + if (selectedCode.code.length > 0) { + copyToClipboard(selectedCode.code, (): void => { + this.onCopiedToClipboard(selectedCode.code, selectedCode.type); + }); + } + } + } + }); + + if (props.codeBlockActions != null) { + Object.keys(props.codeBlockActions).forEach((actionId: string) => { + const validAction = props.codeBlockActions?.[actionId]?.acceptedLanguages == null || props.language == null || props.codeBlockActions?.[actionId]?.acceptedLanguages?.find(acceptedLang => props.language === acceptedLang) != null ? props.codeBlockActions?.[actionId] : undefined; + if (validAction != null) { + this.codeBlockButtons.push(new Button({ + testId: testIds.chatItem.syntaxHighlighter.button, + icon: validAction.icon != null ? new Icon({ icon: validAction.icon }).render : undefined, + label: validAction.label, + attributes: { title: validAction.description ?? '' }, + primary: false, + classNames: [ + ...(props.codeBlockActions?.[actionId]?.flash != null ? [ 'mynah-button-flash-by-parent-focus', `animate-${props.codeBlockActions?.[actionId]?.flash ?? 'infinite'}` ] : [ '' ]) + ], + ...(props.codeBlockActions?.[actionId]?.flash != null + ? { + onHover: (e) => { + if (e.target != null) { + (e.target as HTMLButtonElement).classList.remove('mynah-button-flash-by-parent-focus'); + } + } + } + : {}), + onClick: e => { + cancelEvent(e); + if (e.target != null) { + (e.target as HTMLButtonElement).classList.remove('mynah-button-flash-by-parent-focus'); + } + const selectedCode = this.getSelectedCode(); + if (this.props?.onCodeBlockAction !== undefined) { + this.props.onCodeBlockAction( + validAction.id, + validAction.data, + selectedCode.type, + selectedCode.code, + undefined, + this.props?.index + ); + } + }, + additionalEvents: { mousedown: cancelEvent }, + }).render); + } + }); + } + + const moreContentIndicator = new MoreContentIndicator({ + icon: MynahIcons.DOWN_OPEN, + border: false, + onClick: () => { + if (this.render.hasClass('no-max')) { + this.render.removeClass('no-max'); + moreContentIndicator.update({ + icon: MynahIcons.DOWN_OPEN + }); + } else { + this.render.addClass('no-max'); + moreContentIndicator.update({ + icon: MynahIcons.UP_OPEN + }); + } + } + }); + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: testIds.chatItem.syntaxHighlighter.wrapper, + classNames: [ 'mynah-syntax-highlighter', + ...(props.block !== true ? [ 'mynah-inline-code' ] : [ ]), + ...(props.wrapCodeBlock === true ? [ 'wrap-code-block' ] : [ ]), + ...(props.unlimitedHeight === true ? [ 'no-max' ] : [ ]), + ], + children: [ + preElement, + ...(props.showLineNumbers === true + ? [ + { + type: 'span', + testId: testIds.chatItem.syntaxHighlighter.lineNumbers, + classNames: [ 'line-numbers-rows' ], + children: (preElement.innerHTML).split(/\n/).slice(0, -1).map((n: string, i: number) => ({ + type: 'span', + innerHTML: String(i + (props.startingLineNumber ?? 1)), + })), + } + ] + : []), + ...(this.props.block === true + ? [ + ...(this.props.unlimitedHeight !== true ? [ moreContentIndicator.render ] : []), + { + type: 'div', + testId: testIds.chatItem.syntaxHighlighter.buttonsWrapper, + classNames: [ 'mynah-syntax-highlighter-copy-buttons' ], + children: [ + ...this.codeBlockButtons, + ...(props.language != null && this.props.hideLanguage !== true + ? [ { + type: 'span', + testId: testIds.chatItem.syntaxHighlighter.language, + classNames: [ 'mynah-syntax-highlighter-language' ], + children: [ props.language.replace('diff-', '') ] + } ] + : []), + ], + } + ] + : []) + ] + }); + + setTimeout(() => { + if (this.props.block === true && this.props.unlimitedHeight !== true && preElement.scrollHeight > preElement.clientHeight) { + this.render.addClass('max-height-exceed'); + } + }, 100); + } + + private readonly getSelectedCodeContextMenu = (): { + code: string; + type: CodeSelectionType; + } => ({ + code: document.getSelection()?.toString() ?? '', + type: 'selection' + }); + + private readonly getSelectedCode = (): { + code: string; + type: CodeSelectionType; + } => ({ + code: this.render.querySelector('pre')?.innerText ?? '', + type: 'block' + }); + + private readonly onCopiedToClipboard = ( + textToSendClipboard: string, + type?: CodeSelectionType): void => { + if (this.props?.onCopiedToClipboard != null) { + this.props?.onCopiedToClipboard( + type, + textToSendClipboard, + this.props.index + ); + } + }; +} diff --git a/vendor/mynah-ui/src/components/tabs.ts b/vendor/mynah-ui/src/components/tabs.ts new file mode 100644 index 00000000..cdd27ad7 --- /dev/null +++ b/vendor/mynah-ui/src/components/tabs.ts @@ -0,0 +1,280 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// eslint-disable @typescript-eslint/restrict-template-expressions +import { DomBuilder, ExtendedHTMLElement } from '../helper/dom'; +import { cancelEvent } from '../helper/events'; +import { StyleLoader } from '../helper/style-loader'; +import { Button } from './button'; +import { Icon, MynahIcons, MynahIconsType } from './icon'; +import { Overlay, OverlayHorizontalDirection, OverlayVerticalDirection } from './overlay'; + +export interface ToggleOption { + label?: ExtendedHTMLElement | string | HTMLElement; + icon?: MynahIcons | MynahIconsType | null; + pinned?: boolean; + disabled?: boolean; + selected?: boolean; + value: string; + disabledTooltip?: string | ExtendedHTMLElement; +} +interface TabItemRenderProps extends ToggleOption { + wrapperTestId?: string; + optionTestId?: string; + labelTestId?: string; + closeButtonTestId?: string; + name: string; + onChange?: (selectedValue: string) => void; + onRemove?: (selectedValue: string, domElement: ExtendedHTMLElement) => void; +} +class TabItem { + render: ExtendedHTMLElement; + private readonly props: TabItemRenderProps; + private disabledTooltip?: Overlay; + private disabledTooltipTimer: ReturnType; + constructor (props: TabItemRenderProps) { + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'span', + classNames: [ ...(this.props.pinned === true ? [ 'mynah-tab-item-pinned' ] : [ '' ]) ], + testId: props.wrapperTestId, + attributes: { + key: `${this.props.name}-${this.props.value}`, + title: this.props.label as string ?? '', + }, + events: { + ...(this.props.disabled === true && this.props.disabledTooltip !== undefined + ? { + mouseenter: () => { + this.disabledTooltipTimer = setTimeout(() => { + this.disabledTooltip = new Overlay({ + children: [ { + type: 'span', + classNames: [ 'mynah-tabs-disabled-tooltip-container' ], + children: [ this.props.disabledTooltip ?? '' ] + } ], + closeOnOutsideClick: false, + dimOutside: false, + referenceElement: this.render, + horizontalDirection: OverlayHorizontalDirection.CENTER, + verticalDirection: OverlayVerticalDirection.TO_TOP + }); + }, 500); + }, + mouseleave: () => { + clearTimeout(this.disabledTooltipTimer); + if (this.disabledTooltip !== undefined) { + this.disabledTooltip.close(); + setTimeout(() => { + this.disabledTooltip = undefined; + }, 50); + } + } + } + : {}) + }, + children: [ + { + type: 'input', + testId: props.optionTestId, + classNames: [ 'mynah-tab-item' ], + attributes: { + type: 'radio', + id: `${this.props.name}-${this.props.value}`, + value: this.props.value, + name: this.props.name, + ...(this.props.selected === true ? { checked: 'checked' } : {}), + ...(this.props.disabled === true ? { disabled: 'disabled' } : {}), + }, + events: { + change: () => { + if (this.props.onChange != null) { + this.props.onChange(this.props.value); + } + } + }, + }, + { + type: 'label', + testId: props.labelTestId, + classNames: [ 'mynah-tab-item-label' ], + attributes: { + for: `${this.props.name}-${this.props.value}`, + }, + events: { + dblclick: (e) => { + cancelEvent(e); + }, + auxclick: (e) => { + // only close on middle click + if (e.button === 1 && this.props.onRemove !== undefined && this.props.pinned !== true) { + this.props.onRemove(this.props.value, this.render); + } + }, + }, + children: [ + this.props.icon != null ? new Icon({ icon: props.icon as MynahIcons }).render : '', + { + type: 'span', + classNames: [ 'mynah-tab-item-label-text' ], + children: [ this.props.label ?? '' ] + }, + (this.props.onRemove !== undefined && this.props.pinned !== true) + ? new Button({ + testId: this.props.closeButtonTestId, + classNames: [ 'mynah-tabs-close-button' ], + onClick: () => { + if (this.props.onRemove !== undefined) { + this.props.onRemove(this.props.value, this.render); + } + }, + icon: new Icon({ icon: MynahIcons.CANCEL }).render, + primary: false + }).render + : '' + ], + }, + ], + }); + } +} +export interface TabProps { + testId?: string; + options: ToggleOption[]; + direction?: 'horizontal' | 'vertical'; + value?: string | null; + name: string; + disabled?: boolean; + onChange?: (selectedValue: string) => void; + onRemove?: (selectedValue: string, domElement: ExtendedHTMLElement) => void; +} +export class Tab { + render: ExtendedHTMLElement; + private readonly props: TabProps; + private currentValue?: string | null; + + constructor (props: TabProps) { + StyleLoader.getInstance().load('components/_tab.scss'); + this.props = { direction: 'horizontal', ...props }; + this.currentValue = this.props.value; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: this.props.testId, + classNames: [ 'mynah-tabs-container', `mynah-tabs-direction-${this.props.direction as string}` ], + attributes: props.disabled === true ? { disabled: 'disabled' } : {}, + children: this.getChildren(props.value), + events: { + wheel: { + handler: this.transformScroll, + options: { passive: true } + } + } + }); + } + + private readonly transformScroll = (e: WheelEvent): void => { + if (e.deltaY === 0) { + return; + } + this.render.scrollLeft += e.deltaY; + }; + + private readonly getChildren = (value?: string | null): any[] => [ + ...this.props.options.map(option => { + return new TabItem({ + ...option, + selected: value === option.value, + name: this.props.name, + onChange: this.updateSelectionRender, + onRemove: this.props.onRemove, + ...(this.props.testId != null + ? { + wrapperTestId: `${this.props.testId}-option-wrapper`, + optionTestId: `${this.props.testId}-option`, + labelTestId: `${this.props.testId}-option-label`, + closeButtonTestId: `${this.props.testId}-option-close-button` + } + : {}) + }).render; + }) + ]; + + private readonly updateSelectionRender = (value: string): void => { + if (this.props.onChange !== undefined) { + this.props.onChange(value); + } + }; + + setValue = (value: string): void => { + if (value !== this.getValue()) { + this.currentValue = value; + const elmToCheck = this.render.querySelector(`#${this.props.name}-${value}`); + if (elmToCheck !== undefined) { + (elmToCheck as HTMLInputElement).click(); + (elmToCheck as HTMLInputElement).checked = true; + ((elmToCheck as HTMLInputElement).nextSibling as HTMLLabelElement).classList.remove('indication'); + } + } + }; + + addOption = (option: ToggleOption): void => { + this.props.options.push(option); + this.render.appendChild(new TabItem({ + ...option, + name: this.props.name, + onChange: this.updateSelectionRender, + onRemove: this.props.onRemove, + ...(this.props.testId != null + ? { + wrapperTestId: `${this.props.testId}-options-wrapper`, + optionTestId: `${this.props.testId}-option`, + labelTestId: `${this.props.testId}-option-label`, + closeButtonTestId: `${this.props.testId}-option-close-button` + } + : {}) + }).render); + if (option.selected === true) { + this.setValue(option.value); + this.snapToOption(option.value); + } + }; + + removeOption = (value: string): void => { + this.props.options = this.props.options.filter(option => option.value !== value); + const elmToCheck = this.render.querySelector(`span[key="${this.props.name}-${value}"]`); + if (elmToCheck !== undefined) { + elmToCheck?.remove(); + } + }; + + updateOptionTitle = (value: string, title: string): void => { + this.props.options = this.props.options.filter(option => option.value !== value); + const elmToCheck = this.render.querySelector(`span[key="${this.props.name}-${value}"] .mynah-tab-item-label-text`); + if (elmToCheck !== undefined) { + (elmToCheck as HTMLSpanElement).innerHTML = title; + } + }; + + updateOptionIndicator = (value: string, indication: boolean): void => { + this.props.options = this.props.options.filter(option => option.value !== value); + const elmToCheck: HTMLLabelElement | null = this.render.querySelector(`label[for="${this.props.name}-${value}"]`); + if (elmToCheck !== null) { + if (indication && value !== this.getValue()) { + elmToCheck.classList.add('indication'); + } else { + elmToCheck.classList.remove('indication'); + } + } + }; + + snapToOption = (value: string): void => { + const elmToCheck = this.render.querySelector(`#${this.props.name}-${value}`); + if (elmToCheck !== undefined) { + this.render.scrollLeft = (elmToCheck?.parentNode as HTMLElement).offsetLeft; + } + }; + + getValue = (): string | undefined | null => this.currentValue; +} diff --git a/vendor/mynah-ui/src/components/title-description-with-icon.ts b/vendor/mynah-ui/src/components/title-description-with-icon.ts new file mode 100644 index 00000000..2dd1f0c4 --- /dev/null +++ b/vendor/mynah-ui/src/components/title-description-with-icon.ts @@ -0,0 +1,62 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +// eslint-disable @typescript-eslint/restrict-template-expressions +import { DomBuilder, DomBuilderObject, ExtendedHTMLElement } from '../helper/dom'; +import { StyleLoader } from '../helper/style-loader'; +import { Icon, MynahIcons, MynahIconsType } from './icon'; + +interface TitleDescriptionWithIconProps { + title?: string | ExtendedHTMLElement | HTMLElement | DomBuilderObject; + description?: string | ExtendedHTMLElement | HTMLElement | DomBuilderObject; + icon?: MynahIcons | MynahIconsType; + testId?: string; + classNames?: string[]; +} +export class TitleDescriptionWithIcon { + render: ExtendedHTMLElement; + private readonly props: TitleDescriptionWithIconProps; + constructor (props: TitleDescriptionWithIconProps) { + StyleLoader.getInstance().load('components/_title-description-icon.scss'); + this.props = props; + this.render = DomBuilder.getInstance().build({ + type: 'div', + testId: props.testId, + // Apply icon wrapper styles only if icon is provided + classNames: [ + ...(this.props.icon !== undefined ? [ 'mynah-ui-title-description-icon-wrapper' ] : []), + ...(this.props.classNames ?? []) + ], + children: [ + ...(this.props.icon !== undefined + ? [ { + type: 'div', + testId: `${props.testId ?? ''}-icon`, + classNames: [ 'mynah-ui-title-description-icon-icon' ], + children: [ new Icon({ + icon: this.props.icon + }).render ] + } ] + : []), + ...(this.props.title !== undefined + ? [ { + type: 'div', + testId: `${props.testId ?? ''}-title`, + classNames: [ 'mynah-ui-title-description-icon-title' ], + children: [ this.props.title ] + } ] + : []), + ...(this.props.description !== undefined + ? [ { + type: 'div', + testId: `${props.testId ?? ''}-description`, + classNames: [ 'mynah-ui-title-description-icon-description' ], + children: [ this.props.description ] + } ] + : []) + ] + }); ; + } +} diff --git a/vendor/mynah-ui/src/global.d.ts b/vendor/mynah-ui/src/global.d.ts new file mode 100644 index 00000000..ea9f2dad --- /dev/null +++ b/vendor/mynah-ui/src/global.d.ts @@ -0,0 +1,8 @@ +declare module '*.svg' { + const content: string; + export default content; +} +declare module '*.scss'; +declare const require: { + context: (directory: string, useSubdirectories?: boolean, regExp?: RegExp) => any; +}; diff --git a/vendor/mynah-ui/src/helper/__test__/date-time.spec.ts b/vendor/mynah-ui/src/helper/__test__/date-time.spec.ts new file mode 100644 index 00000000..52f1a99f --- /dev/null +++ b/vendor/mynah-ui/src/helper/__test__/date-time.spec.ts @@ -0,0 +1,38 @@ +import { getTimeDiff } from '../date-time'; + +describe('date-time', () => { + describe('getTimeDiff', () => { + it('minutes', () => { + expect(getTimeDiff(0)).toEqual('1min'); + expect(getTimeDiff(60000)).toEqual('1min'); + expect(getTimeDiff(60000, { minutes: false })).toEqual(''); + expect(getTimeDiff(120000)).toEqual('2min'); + expect(getTimeDiff(180000)).toEqual('3min'); + }); + it('hours', () => { + expect(getTimeDiff(3_600_000)).toEqual('1hr'); + expect(getTimeDiff(3_600_000, { hours: false })).toEqual(''); + expect(getTimeDiff(7_200_000)).toEqual('2hr'); + }); + it('days', () => { + expect(getTimeDiff(86_400_000)).toEqual('1da'); + expect(getTimeDiff(86_400_000, { days: false })).toEqual(''); + expect(getTimeDiff(172_800_000)).toEqual('2da'); + }); + it('weeks', () => { + expect(getTimeDiff(604_800_000)).toEqual('1we'); + expect(getTimeDiff(604_800_000, { weeks: false })).toEqual(''); + expect(getTimeDiff(1_209_600_000)).toEqual('2we'); + }); + it('months', () => { + expect(getTimeDiff(2_592_000_000)).toEqual('1mo'); + expect(getTimeDiff(2_592_000_000, { months: false })).toEqual(''); + expect(getTimeDiff(5_184_000_000)).toEqual('2mo'); + }); + it('years', () => { + expect(getTimeDiff(31_104_000_000)).toEqual('1yr'); + expect(getTimeDiff(31_104_000_000, { years: false })).toEqual(''); + expect(getTimeDiff(62_208_000_000)).toEqual('2yr'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/helper/__test__/dom.spec.ts b/vendor/mynah-ui/src/helper/__test__/dom.spec.ts new file mode 100644 index 00000000..40530317 --- /dev/null +++ b/vendor/mynah-ui/src/helper/__test__/dom.spec.ts @@ -0,0 +1,266 @@ +import { MynahPortalNames } from '../../static'; +import { DomBuilder, DomBuilderObject, DomBuilderObjectFilled } from '../dom'; + +describe('dom', () => { + describe('DomBuilder', () => { + it('build a basic element', () => { + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + attributes: { id: '#testDiv1', draggable: 'true' }, + classNames: [ 'test-class-1', 'test-class-2' ], + innerHTML: 'innerHTML string', + }; + const resultElement = DomBuilder.getInstance().build(mockDomBuilderObject); + expect(resultElement.id).toBe('#testDiv1'); + expect(resultElement.draggable).toBe(true); + expect(Object.values(resultElement.classList)).toEqual([ 'test-class-1', 'test-class-2' ]); + expect(resultElement.innerHTML).toBe('innerHTML string'); + }); + + it('build an element with children', () => { + const domBuilder = DomBuilder.getInstance(); + const mockChildElementBuilderObject: DomBuilderObject = { + type: 'span', + attributes: { id: '#childSpan1' }, + }; + const childElement = domBuilder.build(mockChildElementBuilderObject); + + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + attributes: { id: '#testDiv1', draggable: 'true' }, + classNames: [ 'test-class-1', 'test-class-2' ], + children: [ childElement ], + }; + const resultElement = domBuilder.build(mockDomBuilderObject); + expect(resultElement.childNodes).toHaveLength(1); + expect(resultElement.children[0].outerHTML).toBe(''); + }); + + it('update an element', () => { + const domBuilder = DomBuilder.getInstance(); + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + attributes: { id: '#testDiv1', draggable: 'true' }, + classNames: [ 'test-class-1', 'test-class-2' ], + innerHTML: 'innerHTML string', + }; + const initialElement = domBuilder.build(mockDomBuilderObject); + expect(initialElement.id).toBe('#testDiv1'); + expect(initialElement.draggable).toBe(true); + expect(Object.values(initialElement.classList)).toEqual([ 'test-class-1', 'test-class-2' ]); + expect(initialElement.innerHTML).toBe('innerHTML string'); + + const mockUpdatedDomBuilderObject: DomBuilderObjectFilled = { + attributes: { id: '#testDiv2' }, + classNames: [ 'test-class-3' ], + innerHTML: '', + }; + const updatedElement = domBuilder.update(initialElement, mockUpdatedDomBuilderObject); + expect(updatedElement.id).toBe('#testDiv2'); + expect(updatedElement.draggable).toBe(true); + expect(Object.values(updatedElement.classList)).toEqual([ 'test-class-3' ]); + expect(updatedElement.innerHTML).toBe(''); + }); + }); + + describe('ExtendedHTMLElement', () => { + it('addClass', () => { + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + classNames: [ 'test-class-1' ], + }; + const builtElement = DomBuilder.getInstance().build(mockDomBuilderObject); + builtElement.addClass('test-class-2'); + expect(Object.values(builtElement.classList)).toEqual([ 'test-class-1', 'test-class-2' ]); + }); + + it('removeClass', () => { + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + classNames: [ 'test-class-1', 'test-class-2' ], + }; + const builtElement = DomBuilder.getInstance().build(mockDomBuilderObject); + builtElement.removeClass('test-class-1'); + expect(Object.values(builtElement.classList)).toEqual([ 'test-class-2' ]); + }); + + it('toggleClass', () => { + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + }; + const builtElement = DomBuilder.getInstance().build(mockDomBuilderObject); + builtElement.toggleClass('test-class-1'); + expect(Object.values(builtElement.classList)).toEqual([ 'test-class-1' ]); + builtElement.toggleClass('test-class-1'); + expect(Object.values(builtElement.classList)).toEqual([]); + }); + + it('hasClass', () => { + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + classNames: [ 'test-class-1', 'test-class-2' ], + }; + const resultElement = DomBuilder.getInstance().build(mockDomBuilderObject); + expect(Object.values(resultElement.classList).includes('test-class-1')).toBeTruthy(); + expect(Object.values(resultElement.classList).includes('test-class-2')).toBeTruthy(); + expect(Object.values(resultElement.classList).includes('test-class-3')).toBeFalsy(); + }); + + it('insertChild', () => { + const domBuilder = DomBuilder.getInstance(); + const mockDomBuilderObject: DomBuilderObject = { + type: 'div', + attributes: { id: '#testDiv1' }, + classNames: [ 'test-class-1', 'test-class-2' ], + }; + const resultElement = domBuilder.build(mockDomBuilderObject); + + const childElement1 = domBuilder.build({ + type: 'span', + attributes: { id: '#childSpan1' }, + }); + resultElement.insertChild('beforeend', childElement1); + + const childElement2 = domBuilder.build({ + type: 'span', + attributes: { id: '#childSpan2' }, + }); + resultElement.insertChild('afterbegin', childElement2); + + expect(resultElement.childNodes).toHaveLength(2); + expect(resultElement.children[0].outerHTML).toBe(''); + expect(resultElement.children[1].outerHTML).toBe(''); + }); + + it('remove (children)', () => { + const domBuilder = DomBuilder.getInstance(); + const childElement1 = domBuilder.build({ + type: 'span', + attributes: { id: '#childSpan1' }, + }); + + const childElement2 = domBuilder.build({ + type: 'span', + attributes: { id: '#childSpan2' }, + // should prevent it from being removed + persistent: true, + }); + + const resultElement = domBuilder.build({ + type: 'div', + attributes: { id: '#testDiv1' }, + children: [ childElement1, childElement2 ], + }); + + expect(resultElement.childNodes).toHaveLength(2); + expect(resultElement.children[0].outerHTML).toBe(''); + expect(resultElement.children[1].outerHTML).toBe(''); + + resultElement.clear(); + expect(resultElement.childNodes).toHaveLength(1); + expect(resultElement.children[0].outerHTML).toBe(''); + }); + }); + + describe('portal', () => { + it('createPortal', () => { + document.body.innerHTML = '
'; + + const domBuilder = DomBuilder.getInstance(); + domBuilder.createPortal( + 'testPortal', + { + type: 'div', + attributes: { + id: '#wrapper1', + }, + classNames: [ 'wrapper-class-1' ] + }, + 'afterbegin' + ); + + expect(document.body.children).toHaveLength(2); + expect(document.body.children[0].outerHTML).toBe('
'); + expect(document.body.children[1].outerHTML).toBe('
'); + }); + + it('getPortal', () => { + document.body.innerHTML = '
'; + + const domBuilder = DomBuilder.getInstance(); + domBuilder.createPortal( + 'testPortal', + { + type: 'div', + attributes: { + id: '#wrapper1', + }, + classNames: [ 'wrapper-class-1' ] + }, + 'afterbegin' + ); + + expect(domBuilder.getPortal('testPortal')?.id).toBe('#wrapper1'); + }); + + it('removePortal', () => { + document.body.innerHTML = '
'; + + const domBuilder = DomBuilder.getInstance(); + domBuilder.createPortal( + 'testPortal', + { + type: 'div', + attributes: { + id: '#wrapper1', + }, + classNames: [ 'wrapper-class-1' ] + }, + 'afterbegin' + ); + + domBuilder.removePortal('testPortal'); + + expect(document.body.children).toHaveLength(1); + expect(document.body.children[0].outerHTML).toBe('
'); + }); + + it('removeAllPortals', () => { + document.body.innerHTML = '
'; + + const domBuilder = DomBuilder.getInstance(); + domBuilder.createPortal( + 'wrapper1', + { + type: 'div', + attributes: { + id: '#wrapper1', + }, + classNames: [ 'wrapper-class-1' ] + }, + 'afterbegin' + ); + domBuilder.createPortal( + 'wrapper2', + { + type: 'div', + attributes: { + id: '#wrapper2', + }, + classNames: [ 'wrapper-class-2' ] + }, + 'afterbegin' + ); + + expect(document.body.children).toHaveLength(3); + expect(document.body.children[0].outerHTML).toBe('
'); + expect(document.body.children[1].outerHTML).toBe('
'); + expect(document.body.children[2].outerHTML).toBe('
'); + + domBuilder.removeAllPortals(MynahPortalNames.WRAPPER); + + expect(document.body.children).toHaveLength(1); + expect(document.body.children[0].outerHTML).toBe('
'); + }); + }); +}); diff --git a/vendor/mynah-ui/src/helper/__test__/events.spec.ts b/vendor/mynah-ui/src/helper/__test__/events.spec.ts new file mode 100644 index 00000000..5d66ec95 --- /dev/null +++ b/vendor/mynah-ui/src/helper/__test__/events.spec.ts @@ -0,0 +1,32 @@ +import { MynahEventNames } from '../../static'; +import { MynahUIGlobalEvents } from '../events'; + +describe('events', () => { + it('addListener', () => { + const mockData = 'mockData'; + const mockCopyToClipBoardEventHandler = jest.fn(); + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.COPY_CODE_TO_CLIPBOARD, mockCopyToClipBoardEventHandler); + const mockCardVoteEventHandler = jest.fn(); + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CARD_VOTE, mockCardVoteEventHandler); + + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.COPY_CODE_TO_CLIPBOARD, mockData); + + // Only COPY_CODE_TO_CLIPBOARD event's handler should have been called + expect(mockCardVoteEventHandler.mock.calls).toHaveLength(0); + expect(mockCopyToClipBoardEventHandler.mock.calls).toHaveLength(1); + expect(mockCopyToClipBoardEventHandler.mock.calls[0][0]).toBe(mockData); + }); + + it('removeListener', () => { + const mockData = 'mockData'; + const mockCopyToClipBoardEventHandler = jest.fn(); + const mockEventListenerId = MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.COPY_CODE_TO_CLIPBOARD, mockCopyToClipBoardEventHandler); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.COPY_CODE_TO_CLIPBOARD, mockData); + MynahUIGlobalEvents.getInstance().removeListener(MynahEventNames.COPY_CODE_TO_CLIPBOARD, mockEventListenerId); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.COPY_CODE_TO_CLIPBOARD, mockData); + + // Should only have been called once + expect(mockCopyToClipBoardEventHandler.mock.calls).toHaveLength(1); + expect(mockCopyToClipBoardEventHandler.mock.calls[0][0]).toBe(mockData); + }); +}); diff --git a/vendor/mynah-ui/src/helper/__test__/file-tree.spec.ts b/vendor/mynah-ui/src/helper/__test__/file-tree.spec.ts new file mode 100644 index 00000000..1f4214ab --- /dev/null +++ b/vendor/mynah-ui/src/helper/__test__/file-tree.spec.ts @@ -0,0 +1,30 @@ +import { TreeNode, fileListToTree } from '../file-tree'; + +describe('file tree', () => { + it('fileListToTree', () => { + const modifiedFilePaths = [ 'project/src/hello.js' ]; + const deletedFilePaths = [ 'project/src/goodbye.js' ]; + const correctTreeNode: TreeNode = { + name: 'Changes', + type: 'folder', + children: [ + { + name: 'project', + type: 'folder', + children: [ + { + name: 'src', + type: 'folder', + children: [ + { name: 'hello.js', type: 'file', filePath: 'project/src/hello.js', originalFilePath: 'project/src/hello.js', deleted: false }, + { name: 'goodbye.js', type: 'file', filePath: 'project/src/goodbye.js', originalFilePath: 'project/src/goodbye.js', deleted: true } + ] + } + ] + } + ] + }; + + expect(fileListToTree(modifiedFilePaths, deletedFilePaths)).toEqual(correctTreeNode); + }); +}); diff --git a/vendor/mynah-ui/src/helper/__test__/guid.spec.ts b/vendor/mynah-ui/src/helper/__test__/guid.spec.ts new file mode 100644 index 00000000..a95a33b2 --- /dev/null +++ b/vendor/mynah-ui/src/helper/__test__/guid.spec.ts @@ -0,0 +1,35 @@ +import { generateUID } from '../guid'; + +describe('generateUID', () => { + it('should generate a unique identifier', () => { + const uid1 = generateUID(); + const uid2 = generateUID(); + + expect(uid1).toBeDefined(); + expect(uid2).toBeDefined(); + expect(uid1).not.toBe(uid2); + expect(typeof uid1).toBe('string'); + expect(typeof uid2).toBe('string'); + }); + + it('should generate UIDs of consistent format', () => { + const uid = generateUID(); + + // Should be a string with some length + expect(uid.length).toBeGreaterThan(0); + + // Should contain alphanumeric characters + expect(uid).toMatch(/^[a-zA-Z0-9]+$/); + }); + + it('should generate different UIDs on multiple calls', () => { + const uids = new Set(); + + // Generate 100 UIDs and ensure they're all unique + for (let i = 0; i < 100; i++) { + uids.add(generateUID()); + } + + expect(uids.size).toBe(100); + }); +}); diff --git a/vendor/mynah-ui/src/helper/__test__/style-loader.spec.ts b/vendor/mynah-ui/src/helper/__test__/style-loader.spec.ts new file mode 100644 index 00000000..1284dab7 --- /dev/null +++ b/vendor/mynah-ui/src/helper/__test__/style-loader.spec.ts @@ -0,0 +1,78 @@ +import { StyleLoader } from '../style-loader'; + +describe('StyleLoader', () => { + let styleLoader: StyleLoader; + + beforeEach(() => { + // Clear any existing style elements + document.head.querySelectorAll('style').forEach(el => el.remove()); + styleLoader = StyleLoader.getInstance(); + }); + + afterEach(() => { + // Clean up after each test + document.head.querySelectorAll('style').forEach(el => el.remove()); + }); + + it('should be a singleton', () => { + const loader1 = StyleLoader.getInstance(); + const loader2 = StyleLoader.getInstance(); + + expect(loader1).toBe(loader2); + }); + + it('should load styles', () => { + const stylePath = 'test-style.scss'; + + styleLoader.load(stylePath); + + // Should not throw an error + expect(styleLoader).toBeDefined(); + }); + + it('should handle multiple style loads', () => { + const stylePath1 = 'style1.scss'; + const stylePath2 = 'style2.scss'; + + styleLoader.load(stylePath1); + styleLoader.load(stylePath2); + + expect(styleLoader).toBeDefined(); + }); + + it('should handle duplicate style loads', () => { + const stylePath = 'duplicate-style.scss'; + + styleLoader.load(stylePath); + styleLoader.load(stylePath); // Load same style again + + expect(styleLoader).toBeDefined(); + }); + + it('should handle empty style path', () => { + expect(async () => await styleLoader.load('')).not.toThrow(); + }); + + it('should handle null/undefined style path', () => { + expect(async () => await styleLoader.load(null as any)).not.toThrow(); + expect(async () => await styleLoader.load(undefined as any)).not.toThrow(); + }); + + it('should handle various file extensions', () => { + const styles = [ + 'component.scss', + 'layout.css', + 'theme.sass' + ]; + + styles.forEach(style => { + expect(async () => await styleLoader.load(style)).not.toThrow(); + }); + }); + + it('should handle nested paths', () => { + const nestedPath = 'components/button/_button.scss'; + + expect(async () => await styleLoader.load(nestedPath)).not.toThrow(); + }); +}); diff --git a/vendor/mynah-ui/src/helper/chat-item.ts b/vendor/mynah-ui/src/helper/chat-item.ts new file mode 100644 index 00000000..88e505fd --- /dev/null +++ b/vendor/mynah-ui/src/helper/chat-item.ts @@ -0,0 +1,43 @@ +import { ChatItem, ChatItemContent } from '../static'; + +export const emptyChatItemContent: ChatItemContent = { + header: null, + body: null, + buttons: null, + codeBlockActions: null, + codeReference: null, + customRenderer: null, + fileList: null, + followUp: null, + summary: null, + footer: null, + formItems: null, + informationCard: null, + relatedContent: null, + tabbedContent: null +}; + +export const chatItemHasContent = (chatItem: Partial): boolean => ( + (chatItem.body != null && chatItem.body !== '') || + chatItem.fileList != null || + chatItem.formItems != null || + chatItem.header != null || + chatItem.footer != null || + chatItem.summary != null || + chatItem.customRenderer != null || + chatItem.informationCard != null || + chatItem.buttons != null); + +export const copyToClipboard = async ( + textToSendClipboard: string, + onCopied?: () => void +): Promise => { + if (!document.hasFocus?.()) { + window.focus(); + } + try { + await navigator.clipboard.writeText(textToSendClipboard); + } finally { + onCopied?.(); + } +}; diff --git a/vendor/mynah-ui/src/helper/config.ts b/vendor/mynah-ui/src/helper/config.ts new file mode 100644 index 00000000..17bc7449 --- /dev/null +++ b/vendor/mynah-ui/src/helper/config.ts @@ -0,0 +1,142 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MynahIcons } from '../components/icon'; +import { ComponentOverrides, ConfigModel, ConfigOptions, ConfigTexts } from '../static'; + +interface ConfigFullModel extends ConfigOptions { + texts: ConfigTexts; + componentClasses: ComponentOverrides; +}; + +const configDefaults: ConfigFullModel = { + componentClasses: {}, + maxTabs: 1000, + userInputLengthWarningThreshold: 3500, + maxUserInput: 4096, + showPromptField: true, + autoFocus: true, + tabBarButtons: [], + test: false, + feedbackOptions: [ + { + value: 'inaccurate-response', + label: 'Inaccurate response', + }, + { + value: 'harmful-content', + label: 'Harmful content' + }, + { + value: 'overlap', + label: 'Overlaps with existing content' + }, + { + value: 'incorrect-syntax', + label: 'Incorrect syntax' + }, + { + value: 'buggy-code', + label: 'Buggy code' + }, + { + value: 'low-quality', + label: 'Low quality' + }, + { + value: 'other', + label: 'Other' + } + ], + texts: { + mainTitle: 'AWS Q', + copy: 'Copy', + insertAtCursorLabel: 'Insert at cursor', + feedbackFormTitle: 'Report an issue', + feedbackFormDescription: '', + feedbackFormOptionsLabel: 'What type of issue would you like to report?', + feedbackFormCommentLabel: 'Description of issue (optional):', + feedbackThanks: 'Thanks!', + feedbackReportButtonLabel: 'Report an issue', + codeSuggestions: 'Files', + files: 'file(s)', + changes: 'Changes', + clickFileToViewDiff: 'Click on a file to view diff.', + showMore: 'Show more', + save: 'Save', + cancel: 'Cancel', + submit: 'Submit', + add: 'Add', + pleaseSelect: 'Please select...', + stopGenerating: 'Stop', + copyToClipboard: 'Copied to clipboard', + noMoreTabsTooltip: 'You\'ve reached maximum number of tabs you can simultaneously use.', + codeSuggestionWithReferenceTitle: 'Some suggestions contain code with references.', + spinnerText: 'Amazon Q is generating your answer...', + tabCloseConfirmationMessage: 'Are you sure want to close the tab? Closing the tab would mean that your running job will stop.', + tabCloseConfirmationCloseButton: 'Close tab', + tabCloseConfirmationKeepButton: 'Keep tab', + noTabsOpen: '### Open a tab to chat with Q', + openNewTab: 'New tab', + commandConfirmation: 'Press enter to continue', + pinContextHint: 'Pin context with \u2325 Enter', + dragOverlayText: 'Add image to context', + }, +}; +export class Config { + private static instance: Config | undefined; + public config: ConfigFullModel; + private constructor (config?: Partial) { + this.config = { + ...configDefaults, + ...config, + texts: { + ...configDefaults.texts, + ...config?.texts + }, + componentClasses: { + ...configDefaults.componentClasses, + ...config?.componentOverrides + } + }; + // Set dragOverlayIcon default + if (this.config.dragOverlayIcon === undefined) { + this.config.dragOverlayIcon = MynahIcons.IMAGE; + } + this.config.codeBlockActions = { + ...(this.config.codeCopyToClipboardEnabled !== false + ? { + copy: { + id: 'copy', + label: this.config.texts.copy, + icon: MynahIcons.COPY + } + } + : {}), + ...(this.config.codeInsertToCursorEnabled !== false + ? { + 'insert-to-cursor': { + id: 'insert-to-cursor', + label: this.config.texts.insertAtCursorLabel, + icon: MynahIcons.CURSOR_INSERT + } + } + : {}), + ...config?.codeBlockActions + }; + } + + public static getInstance (config?: Partial): Config { + if (Config.instance === undefined) { + Config.instance = new Config(config); + } + + return Config.instance; + } + + public destroy = (): void => { + Config.instance = undefined; + }; +} diff --git a/vendor/mynah-ui/src/helper/date-time.ts b/vendor/mynah-ui/src/helper/date-time.ts new file mode 100644 index 00000000..05567a8e --- /dev/null +++ b/vendor/mynah-ui/src/helper/date-time.ts @@ -0,0 +1,69 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export const getTimeDiff = (differenceInMs: number, show?: { + years?: boolean; + months?: boolean; + weeks?: boolean; + days?: boolean; + hours?: boolean; + minutes?: boolean; +} | 1 | 2 | 3 | 4 | 5 | 6, separator?: string): string => { + // get total seconds for the difference + let delta = Math.abs(differenceInMs) / 1000; + + // calculate (and subtract) whole years + const years = Math.floor(delta / (86_400 * 30 * 12)); + delta -= years * (86_400 * 30 * 12); + + // calculate (and subtract) whole months + const months = Math.floor(delta / (86_400 * 30)); + delta -= months * (86_400 * 30); + + // calculate (and subtract) whole weeks + const weeks = Math.floor(delta / (86_400 * 7)); + delta -= weeks * (86_400 * 7); + + // calculate (and subtract) whole days + const days = Math.floor(delta / 86_400); + delta -= days * 86_400; + + // calculate (and subtract) whole hours + const hours = Math.floor(delta / 3_600) % 24; + delta -= hours * 3_600; + + // calculate (and subtract) whole minutes + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + + const combined = []; + if (years !== 0 && (show === undefined || typeof show !== 'object' || show.years !== false)) { + combined.push(`${years}yr`); + } + if (months !== 0 && (show === undefined || typeof show !== 'object' || show.months !== false)) { + combined.push(`${months}mo`); + } + if (weeks !== 0 && (show === undefined || typeof show !== 'object' || show.weeks !== false)) { + combined.push(`${weeks}we`); + } + if (days !== 0 && (show === undefined || typeof show !== 'object' || show.days !== false)) { + combined.push(`${days}da`); + } + if (hours !== 0 && (show === undefined || typeof show !== 'object' || show.hours !== false)) { + combined.push(`${hours}hr`); + } + if (minutes !== 0 && (show === undefined || typeof show !== 'object' || show.minutes !== false)) { + combined.push(`${minutes}min`); + } + + if (years + months + weeks + days + hours + minutes === 0) { + return '1min'; + } + + if (show !== undefined && typeof show === 'number') { + combined.splice(show, combined.length); + } + return combined.join(separator ?? ' '); +}; diff --git a/vendor/mynah-ui/src/helper/dom.ts b/vendor/mynah-ui/src/helper/dom.ts new file mode 100644 index 00000000..b398c78b --- /dev/null +++ b/vendor/mynah-ui/src/helper/dom.ts @@ -0,0 +1,414 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import testIds from '../helper/test-ids'; +import { MynahEventNames, MynahPortalNames } from '../static'; +import { Config } from './config'; +import { MynahUIGlobalEvents } from './events'; +import { AllowedTagsInCustomRenderer, AllowedAttributesInCustomRenderer } from './sanitize'; + +/* eslint-disable @typescript-eslint/method-signature-style */ +/* eslint-disable @typescript-eslint/consistent-type-assertions */ +/* eslint-disable @typescript-eslint/prefer-optional-chain */ +/* eslint-disable @typescript-eslint/strict-boolean-expressions */ +export const DS: typeof document.querySelectorAll = document.querySelectorAll.bind(document); + +export type GenericEvents = Extract; +export type DomBuilderEventHandler = (event?: any) => any; +export interface DomBuilderEventHandlerWithOptions { + handler: DomBuilderEventHandler; + options?: AddEventListenerOptions; +} +interface GenericDomBuilderAttributes { + attributes?: Record | undefined; + classNames?: string[] | undefined; + events?: Partial> | undefined; +} + +export interface ChatItemBodyRenderer extends GenericDomBuilderAttributes { + type: AllowedTagsInCustomRenderer; + children?: Array | undefined; + attributes?: Partial> | undefined; +} + +export interface DomBuilderObject extends GenericDomBuilderAttributes{ + type: string; + children?: Array | undefined; + innerHTML?: string | undefined; + testId?: string; + persistent?: boolean | undefined; +} + +export interface DomBuilderObjectFilled { + attributes?: Record; + classNames?: string[]; + events?: Record any>; + children?: Array; + innerHTML?: string | undefined; + testId?: string; + persistent?: boolean; +} + +const EmptyDomBuilderObject: DomBuilderObject = { + type: 'div', + attributes: {}, + classNames: [], + events: {}, + children: [], + innerHTML: undefined, + persistent: false, +}; + +export interface ExtendedHTMLElement extends HTMLInputElement { + addClass(className: string): ExtendedHTMLElement; + removeClass(className: string): ExtendedHTMLElement; + toggleClass(className: string): ExtendedHTMLElement; + hasClass(className: string): boolean; + insertChild( + position: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend', + child: string | DomBuilderObject | HTMLElement | ExtendedHTMLElement | Array + ): ExtendedHTMLElement; + clear(removePersistent?: boolean): ExtendedHTMLElement; + builderObject: DomBuilderObject; + update(builderObject: DomBuilderObjectFilled): ExtendedHTMLElement; +} + +export class DomBuilder { + private static instance: DomBuilder | undefined; + private rootFocus: boolean; + private readonly resizeObserver: ResizeObserver; + private rootBox: DOMRect; + root: ExtendedHTMLElement; + private portals: Record = {}; + + private constructor (rootSelector: string) { + this.root = DS(rootSelector)[0] as ExtendedHTMLElement; + this.extendDomFunctionality(this.root); + this.root.addClass('mynah-ui-root'); + this.rootFocus = this.root.matches(':focus') ?? false; + this.attachRootFocusListeners(); + if (ResizeObserver != null) { + this.rootBox = this.root.getBoundingClientRect(); + this.resizeObserver = new ResizeObserver((entry) => { + const incomingRootBox = this.root.getBoundingClientRect(); + // Known issue of ResizeObserver, triggers twice for each size change. + // Check if size was really changed then trigger + if (this.rootBox.height !== incomingRootBox.height || this.rootBox.width !== incomingRootBox.width) { + this.rootBox = incomingRootBox; + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.ROOT_RESIZE, { clientRect: this.rootBox }); + } + }); + this.resizeObserver.observe(this.root); + } + } + + private readonly attachRootFocusListeners = (): void => { + this.root?.setAttribute('tabindex', '0'); + this.root?.setAttribute('autofocus', 'true'); + this.root?.style.setProperty('outline', 'none'); + this.root?.addEventListener('focusin', this.onRootFocus, { capture: true }); + window.addEventListener('blur', this.onRootBlur); + }; + + private readonly onRootFocus = (e: FocusEvent): void => { + if (!this.rootFocus) { + this.rootFocus = true; + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.ROOT_FOCUS, { focusState: this.rootFocus }); + } + }; + + private readonly onRootBlur = (e: FocusEvent): void => { + if (this.rootFocus) { + this.rootFocus = false; + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.ROOT_FOCUS, { focusState: this.rootFocus }); + } + }; + + public readonly setFocusToRoot = (): void => { + this.root?.focus(); + }; + + public static getInstance (rootSelector?: string): DomBuilder { + if (!DomBuilder.instance) { + DomBuilder.instance = new DomBuilder(rootSelector != null ? rootSelector : 'body'); + } + if (rootSelector != null) { + DomBuilder.instance.setRoot(rootSelector); + } + return DomBuilder.instance; + } + + setRoot = (rootSelector?: string): void => { + this.resizeObserver.unobserve(this.root); + this.root.removeEventListener('focus', this.onRootFocus); + window.removeEventListener('blur', this.onRootBlur); + this.root = this.extendDomFunctionality((DS(rootSelector ?? 'body')[0] ?? document.body) as HTMLElement); + this.attachRootFocusListeners(); + this.resizeObserver.observe(this.root); + }; + + addClass = function (this: ExtendedHTMLElement, className: string): ExtendedHTMLElement { + if (className !== '') { + this.classList.add(className); + // eslint-disable-next-line @typescript-eslint/prefer-includes + if (this.builderObject?.classNames?.indexOf(className) === -1) { + this.builderObject.classNames = [ ...this.builderObject.classNames, className ]; + } + } + return this; + }; + + removeClass = function (this: ExtendedHTMLElement, className: string): ExtendedHTMLElement { + this.classList.remove(className); + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain + if (this.builderObject.classNames !== undefined && this.builderObject.classNames.includes(className)) { + this.builderObject.classNames.splice(this.builderObject.classNames.indexOf(className), 1); + } + return this; + }; + + toggleClass = function (this: ExtendedHTMLElement, className: string): ExtendedHTMLElement { + if (this.classList.contains(className)) { + this.removeClass(className); + } else { + this.addClass(className); + } + return this; + }; + + hasClass = function (this: ExtendedHTMLElement, className: string): boolean { + return this.classList.contains(className); + }; + + insertChild = function ( + this: ExtendedHTMLElement, + position: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend', + child: string | HTMLElement | ExtendedHTMLElement | Array + ): ExtendedHTMLElement { + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + if (child) { + if (child instanceof Array) { + child.forEach(childItem => { + if (childItem instanceof Element) { + this.insertAdjacentElement(position, childItem); + } else if (typeof childItem === 'string') { + this.insertAdjacentText(position, childItem); + } + }); + } else { + if (child instanceof Element) { + this.insertAdjacentElement(position, child); + } else if (typeof child === 'string') { + this.insertAdjacentText(position, child); + } + } + } + return this; + }; + + clearChildren = function (this: ExtendedHTMLElement, removePersistent: boolean): ExtendedHTMLElement { + Array.from(this.childNodes).forEach((child: ExtendedHTMLElement | ChildNode) => { + if ( + removePersistent || + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + !(child as ExtendedHTMLElement).builderObject || + (child as ExtendedHTMLElement).builderObject.persistent !== true + ) { + child.remove(); + } + }); + return this; + }; + + extendDomFunctionality = function (this: DomBuilder, domElement: HTMLElement): ExtendedHTMLElement { + const extendedElement: ExtendedHTMLElement = domElement as ExtendedHTMLElement; + extendedElement.addClass = this.addClass.bind(extendedElement); + extendedElement.removeClass = this.removeClass.bind(extendedElement); + extendedElement.toggleClass = this.toggleClass.bind(extendedElement); + extendedElement.hasClass = this.hasClass.bind(extendedElement); + extendedElement.insertChild = this.insertChild.bind(extendedElement); + extendedElement.clear = this.clearChildren.bind(extendedElement); + return extendedElement; + }; + + build = (domBuilderObject: DomBuilderObject): ExtendedHTMLElement => { + const readyToBuildObject: DomBuilderObject = { ...EmptyDomBuilderObject, ...domBuilderObject }; + const buildedDom = document.createElement(readyToBuildObject.type); + + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + buildedDom.classList.add(...(readyToBuildObject.classNames?.filter(className => className !== '') || [])); + + (Object.keys(readyToBuildObject.events ?? {}) as Array>).forEach((eventName: GenericEvents) => { + if (readyToBuildObject?.events !== undefined) { + if (typeof readyToBuildObject?.events[eventName] === 'function') { + buildedDom.addEventListener(eventName, readyToBuildObject.events[eventName] as (event?: any) => any); + } else if (typeof readyToBuildObject?.events[eventName] === 'object') { + buildedDom.addEventListener( + eventName, + (readyToBuildObject.events[eventName] as DomBuilderEventHandlerWithOptions).handler, + (readyToBuildObject.events[eventName] as DomBuilderEventHandlerWithOptions).options ?? undefined + ); + } + if (eventName === 'dblclick' || eventName === 'click') { + buildedDom.classList.add('mynah-ui-clickable-item'); + } + } + }); + + Object.keys(readyToBuildObject.attributes ?? {}).forEach(attributeName => + buildedDom.setAttribute(attributeName, readyToBuildObject.attributes !== undefined ? readyToBuildObject.attributes[attributeName].toString() : '') + ); + + if (readyToBuildObject.testId != null && Config.getInstance().config.test) { + buildedDom.setAttribute(testIds.selector, readyToBuildObject.testId); + } + + if (typeof readyToBuildObject.innerHTML === 'string') { + buildedDom.innerHTML = readyToBuildObject.innerHTML; + } else if (readyToBuildObject.children !== undefined && readyToBuildObject.children?.length > 0) { + this.insertChild.apply(buildedDom as ExtendedHTMLElement, [ + 'beforeend', + [ + ...readyToBuildObject.children.map((child: string | ExtendedHTMLElement | HTMLElement | DomBuilderObject) => { + if (typeof child === 'string' || child instanceof HTMLElement) { + return child; + } + return this.build(child); + }), + ], + ]); + } + + (buildedDom as ExtendedHTMLElement).builderObject = readyToBuildObject; + (buildedDom as ExtendedHTMLElement).update = (builderObject: DomBuilderObjectFilled): ExtendedHTMLElement => { + return this.update(buildedDom as ExtendedHTMLElement, builderObject); + }; + this.extendDomFunctionality(buildedDom); + return buildedDom as ExtendedHTMLElement; + }; + + update = function (domToUpdate: ExtendedHTMLElement, domBuilderObject: DomBuilderObjectFilled): ExtendedHTMLElement { + if (domToUpdate.builderObject) { + if (domBuilderObject.classNames !== undefined) { + domToUpdate.classList.remove(...(domToUpdate.builderObject.classNames as string[])); + domToUpdate.classList.add(...domBuilderObject.classNames.filter(className => className !== '')); + } + + (Object.keys(domBuilderObject.events ?? {}) as Array>).forEach(eventName => { + if (domToUpdate.builderObject.events !== undefined && domToUpdate.builderObject.events[eventName]) { + domToUpdate.removeEventListener( + eventName, + (domToUpdate.builderObject.events[eventName] as DomBuilderEventHandlerWithOptions).handler ?? domToUpdate.builderObject.events[eventName] + ); + } + if (domBuilderObject.events !== undefined && domBuilderObject.events[eventName] !== undefined) { + domToUpdate.addEventListener(eventName, domBuilderObject.events[eventName]); + } + }); + + Object.keys(domBuilderObject.attributes ?? {}).forEach(attributeName => { + if (domBuilderObject.attributes !== undefined && domBuilderObject.attributes[attributeName] === undefined) { + domToUpdate.removeAttribute(attributeName); + } else if (domBuilderObject.attributes !== undefined) { + domToUpdate.setAttribute(attributeName, domBuilderObject.attributes[attributeName] as string); + } + }); + + if (domBuilderObject.testId != null && Config.getInstance().config.test) { + domToUpdate.setAttribute(testIds.selector, domBuilderObject.testId); + } + + if (typeof domBuilderObject.innerHTML === 'string') { + domToUpdate.innerHTML = domBuilderObject.innerHTML; + } else if (domBuilderObject.children !== undefined && domBuilderObject.children.length > 0) { + domToUpdate.clear(); + domToUpdate.insertChild('beforeend', domBuilderObject.children); + } + + domToUpdate.builderObject = { ...EmptyDomBuilderObject, ...domBuilderObject } as DomBuilderObject; + } else { + console.warn('element was not created with dom builder'); + } + return domToUpdate; + }; + + createPortal = ( + portalName: string, + builderObject: DomBuilderObject, + position: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend' + ): ExtendedHTMLElement => { + const portalDom = this.build(builderObject); + this.root.insertChild(position || 'beforeend', portalDom); + this.portals[portalName] = portalDom; + return portalDom; + }; + + getPortal = (portalName: string): ExtendedHTMLElement | undefined => this.portals[portalName] ?? undefined; + removePortal = (portalName: string): void => this.portals[portalName]?.remove(); + removeAllPortals = (portalsWithName: MynahPortalNames): void => { + Object.keys(this.portals).forEach(portalName => { + if (portalName.match(portalsWithName) !== null) { + this.portals[portalName].remove(); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.portals[portalName]; + } + }); + }; + + destroy = (): void => { + DomBuilder.instance = undefined; + }; +} + +export const htmlDecode = (input: string): string => { + const e = document.createElement('textarea'); + e.innerHTML = input; + return e.childNodes.length === 0 ? '' : e.childNodes[0].nodeValue ?? input; +}; + +export const getTypewriterPartsCss = ( + typewriterId: string, + lastVisibleItemIndex: number, + totalNumberOfItems: number, + timeForEach: number): ExtendedHTMLElement => + DomBuilder.getInstance().build({ + type: 'style', + attributes: { + type: 'text/css' + }, + persistent: true, + innerHTML: ` + ${new Array(Math.max(0, totalNumberOfItems)) + .fill(null) + .map((n, i) => { + if (i < lastVisibleItemIndex) { + return ` + .${typewriterId} .typewriter-part[index="${i}"] { + opacity: 1 !important; + animation: none; + } + `; + } + return ` + .${typewriterId} .typewriter-part[index="${i}"] { + opacity: 0; + animation: typewriter-reveal ${50 + timeForEach}ms linear forwards; + animation-delay: ${(i - lastVisibleItemIndex) * timeForEach}ms; + } + `; + }) + .join('')} + `, + }); + +export const cleanupElement = (elm: HTMLElement): void => { + if (elm.querySelectorAll !== undefined) { + Array.from(elm.querySelectorAll('*:empty:not(img, br, hr, input[type="checkbox"])')).forEach(emptyElement => { + if (emptyElement.classList.length === 0) { + emptyElement.remove(); + } + }); + } +}; diff --git a/vendor/mynah-ui/src/helper/events.ts b/vendor/mynah-ui/src/helper/events.ts new file mode 100644 index 00000000..7bc6a440 --- /dev/null +++ b/vendor/mynah-ui/src/helper/events.ts @@ -0,0 +1,73 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable @typescript-eslint/no-dynamic-delete */ +import { MynahEventNames } from '../static'; +import { generateUID } from './guid'; + +export const cancelEvent = (event: Event): boolean => { + event.preventDefault?.(); + event.stopPropagation?.(); + event.stopImmediatePropagation?.(); + return false; +}; + +export class MynahUIGlobalEvents { + private static instance: MynahUIGlobalEvents | undefined; + private readonly listeners: Record void>>; + + private constructor () { + this.listeners = { ...this.listeners }; + } + + public static getInstance = (): MynahUIGlobalEvents => { + if (MynahUIGlobalEvents.instance === undefined) { + MynahUIGlobalEvents.instance = new MynahUIGlobalEvents(); + } + + return MynahUIGlobalEvents.instance; + }; + + /** + * Subscribe to value changes of a specific item in data store + * @param eventKey One of the keys in MynahUIDataModel + * @param handler function will be called with optional data field + * @returns listenerId which will be necessary to removeListener + */ + public addListener = (eventKey: MynahEventNames, handler: (data?: any) => void): string => { + const listenerId: string = generateUID(); + if (this.listeners[eventKey] === undefined) { + this.listeners[eventKey] = {}; + } + this.listeners[eventKey][listenerId] = handler; + return listenerId; + }; + + /** + * Unsubscribe from changes of a specific item in data store + * @param eventKey One of the keys in MynahUIDataModel + * @param listenerId listenerId which is returned from addListener function + */ + public removeListener = (eventKey: MynahEventNames, listenerId: string): void => { + if (this.listeners[eventKey]?.[listenerId] !== undefined) { + delete this.listeners[eventKey][listenerId]; + } + }; + + /** + * Updates the store and informs the subscribers. + * @param data A full or partial set of store data model with values. + */ + public dispatch = (eventKey: MynahEventNames, data?: any): void => { + if (this.listeners[eventKey] !== undefined) { + Object.keys(this.listeners[eventKey]).forEach((listenerId: string) => { + this.listeners[eventKey][listenerId](data); + }); + } + }; + + public destroy = (): void => { + MynahUIGlobalEvents.instance = undefined; + }; +} diff --git a/vendor/mynah-ui/src/helper/file-tree.ts b/vendor/mynah-ui/src/helper/file-tree.ts new file mode 100644 index 00000000..b6e21b9f --- /dev/null +++ b/vendor/mynah-ui/src/helper/file-tree.ts @@ -0,0 +1,138 @@ +/*! + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { MynahIcons, MynahIconsType } from '../main'; +import { FileNodeAction, Status, TreeNodeDetails } from '../static'; +import { Config } from './config'; + +export type TreeNode = FolderNode | FileNode; +export interface FileNode { + name: string; + type: 'file'; + filePath: string; + originalFilePath: string; + deleted: boolean; + actions?: FileNodeAction[]; + details?: TreeNodeDetails; +} +export interface FolderNode { + name: string; + type: 'folder'; + details?: TreeNodeDetails; + children: Array; +} + +/* + * Converts a list of file Paths into a tree + * + * @input: The list of path in string format + * Example Input: + * modifiedFilePaths: [ + * "project/src/hello.js", + * ] + * deletedFilePaths: [ + * "project/src/goodbye.js", + * ] + * + * Example output: + * { + * name: 'Changes', + * type: 'folder', + * children: [{ + * name: 'project', + * type: 'folder', + * children: [{ + * name: 'src', + * type: 'folder', + * children: [ + * { name: 'hello.js', type: 'file', filePath: 'project/src/hello.js', deleted: false }, + * { name: 'goodbye.js', type: 'file', filePath: 'project/src/goodbye.js', deleted: true }, + * ] + * }] + * }] + * } + */ +export const fileListToTree = ( + modifiedFilePaths: string[], + deletedFilePaths: string[] = [], + actions?: Record, + details?: Record, + rootTitle?: string, + rootStatusIcon?: MynahIcons | MynahIconsType, + rootStatusIconForegroundStatus?: Status, + rootLabel?: string, +): TreeNode => { + return [ ...splitFilePaths(modifiedFilePaths, false), ...splitFilePaths(deletedFilePaths, true) ].reduce( + (acc, { originalFilePath, filePath, deleted }) => { + let currentNode = acc; + const currentPathParts: string[] = []; + + for (let i = 0; i < filePath.length; i++) { + const fileOrFolder = filePath[i]; + currentPathParts.push(fileOrFolder); + const currentFullPath = currentPathParts.join('/'); + + if (i === filePath.length - 1) { + // Handle file (last item in path) + (currentNode as FolderNode).children?.push({ + type: 'file', + name: fileOrFolder, + filePath: currentFullPath, + originalFilePath, + deleted, + actions: actions?.[originalFilePath], + details: details?.[originalFilePath], + }); + break; + } else { + // Handle folder + const oldItem = (currentNode as FolderNode).children?.find( + (item) => + item.type === 'folder' && + item.name === fileOrFolder && + // Compare the current path depth + item.children.some(child => + child.type === 'file' + ? child.filePath.startsWith(currentFullPath + '/') + : true + ) + ); + + if (oldItem != null) { + currentNode = oldItem; + } else { + const newItem: FolderNode = { + name: fileOrFolder, + type: 'folder', + children: [], + }; + (currentNode as FolderNode).children?.push(newItem); + currentNode = newItem; + } + } + } + return acc; + }, + { + name: rootTitle ?? Config.getInstance().config.texts.changes, + type: 'folder', + children: [], + ...(rootLabel != null || rootStatusIcon != null || rootStatusIconForegroundStatus != null + ? { + details: { + label: rootLabel, + icon: rootStatusIcon, + iconForegroundStatus: rootStatusIconForegroundStatus, + } + } + : {}) + } + ); +}; + +const splitFilePaths = (paths: string[], deleted: boolean): Array<{ originalFilePath: string; filePath: string[]; deleted: boolean }> => + paths + // split file path by folder. ignore dot folders + .map(filePath => ({ originalFilePath: filePath, filePath: filePath.split('/').filter(item => item !== undefined && item !== '.'), deleted })); diff --git a/vendor/mynah-ui/src/helper/guid.ts b/vendor/mynah-ui/src/helper/guid.ts new file mode 100644 index 00000000..3e26b692 --- /dev/null +++ b/vendor/mynah-ui/src/helper/guid.ts @@ -0,0 +1,10 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export const generateUID = (): string => { + const firstPart: number = (Math.random() * 46656) | 0; + const secondPart: number = (Math.random() * 46656) | 0; + return `000${firstPart.toString(36)}`.slice(-3) + `000${secondPart.toString(36)}`.slice(-3); +}; diff --git a/vendor/mynah-ui/src/helper/marked.ts b/vendor/mynah-ui/src/helper/marked.ts new file mode 100644 index 00000000..eb81e21b --- /dev/null +++ b/vendor/mynah-ui/src/helper/marked.ts @@ -0,0 +1,37 @@ +import { marked, Tokens } from 'marked'; + +export const parseMarkdown = (markdownString: string, options?: {includeLineBreaks?: boolean; inline?: boolean}): string => { + return options?.inline === true + ? marked.parseInline(markdownString, { + breaks: options?.includeLineBreaks, + }) as string + : marked.parse(markdownString, { + breaks: options?.includeLineBreaks, + }) as string; +}; + +export const configureMarked = (): void => { + // Apply global fix for marked listitem content is not getting parsed. + marked.use({ + renderer: { + listitem: (item: Tokens.ListItem) => `
  • + ${item.task ? `` : ''} + ${(item.task ? marked.parseInline : marked.parse)(item.text, { breaks: false }) as string} +
  • `, + link: (token) => { + const pattern = /^\[(?:\[([^\]]+)\]|([^\]]+))\]\(([^)]+)\)$/; + // Expect raw formatted only in [TEXT](URL) + if (!pattern.test(token.raw)) { + return token.href; + } + return `${token.text}`; + } + }, + extensions: [ { + name: 'text', + renderer: (token) => { + return token.text; + } + } ] + }); +}; diff --git a/vendor/mynah-ui/src/helper/merge-html-plugin.ts b/vendor/mynah-ui/src/helper/merge-html-plugin.ts new file mode 100644 index 00000000..3ca2fe3f --- /dev/null +++ b/vendor/mynah-ui/src/helper/merge-html-plugin.ts @@ -0,0 +1,129 @@ +/* + Highlight.js does not support unescaped HTML by default to prevent XSS. + This plugin allows this, so that we can implement highlights with tooltips. + + Taken from: https://github.com/highlightjs/highlight.js/issues/2889 +*/ + +import { HighlightResult, HLJSPlugin } from 'highlight.js'; + +export const mergeHTMLPlugin = (function () { + let originalStream: Event[]; + + function escapeHTML (value: string): string { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + + const mergeHTMLPlugin: HLJSPlugin = { + 'before:highlightElement': ({ el }: {el: Node}) => { + originalStream = nodeStream(el); + }, + 'after:highlightElement': ({ el, result, text }: {el: Element; result: HighlightResult; text: string}) => { + if (originalStream.length === 0) return; + + const resultNode = document.createElement('div'); + resultNode.innerHTML = result.value; + result.value = mergeStreams(originalStream, nodeStream(resultNode), text); + el.innerHTML = result.value; + } + }; + + interface Event { + event: 'start' | 'stop'; + offset: number; + node: Node; + } + + function tag (node: Node): string { + return node.nodeName.toLowerCase(); + } + + function nodeStream (node: Node): Event[] { + const result: Event[] = []; + (function _nodeStream (node, offset) { + for (let child = node.firstChild; child != null; child = child.nextSibling) { + if (child.nodeType === 3) { + offset += child.nodeValue?.length ?? 0; + } else if (child.nodeType === 1) { + result.push({ + event: 'start', + offset, + node: child + }); + offset = _nodeStream(child, offset); + if (tag(child).match(/br|hr|img|input/) == null) { + result.push({ + event: 'stop', + offset, + node: child + }); + } + } + } + return offset; + })(node, 0); + return result; + } + + function mergeStreams (original: Event[], highlighted: Event[], value: string): string { + let processed = 0; + let result = ''; + const nodeStack = []; + + function selectStream (): Event[] { + if ((original.length === 0) || (highlighted.length === 0)) { + return (original.length > 0) ? original : highlighted; + } + if (original[0].offset !== highlighted[0].offset) { + return (original[0].offset < highlighted[0].offset) ? original : highlighted; + } + + return highlighted[0].event === 'start' ? original : highlighted; + } + + function open (node: Node): void { + function attributeString (attr: Attr): string { + return ' ' + attr.nodeName + '="' + escapeHTML(attr.value) + '"'; + } + // @ts-expect-error + result += '<' + tag(node) + [].map.call(node.attributes, attributeString).join('') + '>'; + } + + function close (node: Node): void { + result += ''; + } + + function render (event: Event): void { + (event.event === 'start' ? open : close)(event.node); + } + + while ((original.length > 0) || (highlighted.length > 0)) { + let stream = selectStream(); + result += escapeHTML(value.substring(processed, stream[0].offset)); + processed = stream[0].offset; + if (stream === original) { + nodeStack.reverse().forEach(close); + do { + render(stream.splice(0, 1)[0]); + stream = selectStream(); + } while (stream === original && (stream.length > 0) && stream[0].offset === processed); + nodeStack.reverse().forEach(open); + } else { + if (stream[0].event === 'start') { + nodeStack.push(stream[0].node); + } else { + nodeStack.pop(); + } + render(stream.splice(0, 1)[0]); + } + } + return result + escapeHTML(value.substr(processed)); + } + + return mergeHTMLPlugin; +}()); diff --git a/vendor/mynah-ui/src/helper/quick-pick-data-handler.ts b/vendor/mynah-ui/src/helper/quick-pick-data-handler.ts new file mode 100644 index 00000000..5a8908ea --- /dev/null +++ b/vendor/mynah-ui/src/helper/quick-pick-data-handler.ts @@ -0,0 +1,185 @@ +import escapeHTML from 'escape-html'; +import { DetailedListItem, DetailedListItemGroup, QuickActionCommand, QuickActionCommandGroup } from '../static'; +import { MynahIcons } from '../main'; + +export const filterQuickPickItems = (commands: QuickActionCommandGroup[], searchTerm: string, hideSearchGroup?: boolean): QuickActionCommandGroup[] => { + if (searchTerm.trim() === '') { + return commands; + } + + const matchedCommands: Array<{score: number; command: QuickActionCommand}> = []; + + const findMatches = (cmd: QuickActionCommand): void => { + const score = calculateItemScore(cmd.command, searchTerm); + if (score > 0) { + matchedCommands.push({ + score, + command: { + ...cmd, + // Update command with highlighted text + // It is being reverted when user makes the selection + command: highlightMatch(escapeHTML(cmd.command), searchTerm) + } + }); + } + + // Search for children + cmd.children?.forEach(childGroup => { + childGroup.commands.forEach(childCmd => { + findMatches(childCmd); + }); + }); + }; + + // Filter all commands + commands.forEach(group => { + group.commands.forEach(cmd => { + findMatches(cmd); + }); + }); + + const returnGroup: QuickActionCommandGroup = { + icon: MynahIcons.SEARCH, + commands: [] + }; + if (matchedCommands.length > 0) { + returnGroup.commands = matchedCommands.sort((a, b) => (b.score ?? 0) - (a.score ?? 0)) + .map((item) => item.command); + } + if (hideSearchGroup !== true) { returnGroup.groupName = `### ${searchTerm}: (${returnGroup.commands.length})`; } + return [ returnGroup ]; +}; + +export const MARK_OPEN = ''; +export const MARK_CLOSE = ''; + +const highlightMatch = (text: string, searchTerm: string): string => { + const textToCompare = text.toLowerCase(); + const searchTermToCompare = searchTerm.toLowerCase(); + + // Exact + if (textToCompare === searchTermToCompare) { + return `${MARK_OPEN}${text}${MARK_CLOSE}`; + } + + // Prefix + if (textToCompare.startsWith(searchTermToCompare)) { + const matchLength = searchTerm.length; + const matchedPart = text.slice(0, matchLength); + const restPart = text.slice(matchLength); + return `${MARK_OPEN}${matchedPart}${MARK_CLOSE}${restPart}`; + } + + // Contains + const startIndex = textToCompare.indexOf(searchTermToCompare); + if (startIndex !== -1) { + const before = text.slice(0, startIndex); + const match = text.slice(startIndex, startIndex + searchTerm.length); + const after = text.slice(startIndex + searchTerm.length); + return `${before}${MARK_OPEN}${match}${MARK_CLOSE}${after}`; + } + + // Words + const words = text.split(' '); + for (let i = 0; i < words.length; i++) { + const word = words[i].toLowerCase(); + if (word.includes(searchTermToCompare)) { + const startIdx = word.indexOf(searchTermToCompare); + const originalWord = words[i]; + words[i] = + originalWord.slice(0, startIdx) + + `${MARK_OPEN}${originalWord.slice(startIdx, startIdx + searchTerm.length)}${MARK_CLOSE}` + + originalWord.slice(startIdx + searchTerm.length); + return words.join(' '); + } + } + + // Partial + let result = ''; + let lastIndex = 0; + let termIndex = 0; + + for (let i = 0; i < text.length && termIndex < searchTerm.length; i++) { + if (text[i].toLowerCase() === searchTerm[termIndex].toLowerCase()) { + result += text.slice(lastIndex, i); + result += `${MARK_OPEN}${text[i]}${MARK_CLOSE}`; + lastIndex = i + 1; + termIndex++; + } + } + result += text.slice(lastIndex); + + return termIndex === searchTerm.length ? result : text; +}; + +const calculateItemScore = (text: string, searchTerm: string): number => { + const normalizedText = text.toLowerCase(); + const normalizedTerm = searchTerm.toLowerCase(); + + const isExactMatch = normalizedText === normalizedTerm; + const isPrefixMatch = normalizedText.startsWith(normalizedTerm); + const isWordStartMatch = normalizedText.split(' ').some(word => word.startsWith(normalizedTerm)); + const isContainsMatch = normalizedText.includes(normalizedTerm); + + if (isExactMatch) return 100; + if (isPrefixMatch) return 80; + if (isWordStartMatch) return 60; + if (isContainsMatch) return 40; + + return 0; +}; + +export const convertDetailedListGroupsToQuickActionCommandGroups = (detailedListItemGroups: DetailedListItemGroup[]): QuickActionCommandGroup[] => { + return detailedListItemGroups.map(detailedListItemGroup => ({ + commands: detailedListItemGroup.children?.map(detailedListItem => convertDetailedListItemToQuickActionCommand(detailedListItem)) as QuickActionCommand[], + actions: detailedListItemGroup.actions, + groupName: detailedListItemGroup.groupName, + icon: detailedListItemGroup.icon + })); +}; + +export const convertDetailedListItemToQuickActionCommand = (detailedListItem: DetailedListItem): QuickActionCommand => { + return { + command: detailedListItem.title ?? '', + label: detailedListItem.name, + ...(detailedListItem.children != null ? { children: convertDetailedListGroupsToQuickActionCommandGroups(detailedListItem.children) } : {}), + description: detailedListItem.description, + disabled: detailedListItem.disabled, + icon: detailedListItem.icon, + id: detailedListItem.id, + placeholder: detailedListItem.followupText, + route: detailedListItem.keywords, + }; +}; + +export const convertQuickActionCommandGroupsToDetailedListGroups = (quickActionCommandGroup: QuickActionCommandGroup[]): DetailedListItemGroup[] => { + return quickActionCommandGroup.map(quickActionCommandGroup => ({ + children: quickActionCommandGroup.commands?.map(quickActionCommand => convertQuickActionCommandToDetailedListItem(quickActionCommand)), + actions: quickActionCommandGroup.actions, + groupName: quickActionCommandGroup.groupName, + icon: quickActionCommandGroup.icon + })); +}; + +export const convertQuickActionCommandToDetailedListItem = (quickActionCommand: QuickActionCommand): DetailedListItem => { + return { + title: quickActionCommand.command, + name: quickActionCommand.label, + followupText: quickActionCommand.placeholder, + ...(quickActionCommand.children != null ? { children: convertQuickActionCommandGroupsToDetailedListGroups(quickActionCommand.children) } : {}), + description: quickActionCommand.description, + disabled: quickActionCommand.disabled, + icon: quickActionCommand.icon, + id: quickActionCommand.id, + keywords: quickActionCommand.route, + disabledText: quickActionCommand.disabledText + }; +}; + +export const chunkArray = (array: T[], chunkSize: number): T[][] => { + const result: T[][] = []; + for (let i = 0; i < array.length; i += chunkSize) { + result.push(array.slice(i, i + chunkSize)); + } + return result; +}; diff --git a/vendor/mynah-ui/src/helper/sanitize.ts b/vendor/mynah-ui/src/helper/sanitize.ts new file mode 100644 index 00000000..ba9b9f2a --- /dev/null +++ b/vendor/mynah-ui/src/helper/sanitize.ts @@ -0,0 +1,166 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import sanitizeHtml from 'sanitize-html'; + +export const AllowedTags = [ + 'a', + 'audio', + 'b', + 'blockquote', + 'br', + 'hr', + 'canvas', + 'code', + 'col', + 'colgroup', + 'data', + 'div', + 'em', + 'embed', + 'figcaption', + 'figure', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'i', + 'iframe', + 'img', + 'input', + 'li', + 'map', + 'mark', + 'object', + 'ol', + 'p', + 'pre', + 'q', + 's', + 'small', + 'source', + 'span', + 'strong', + 'sub', + 'sup', + 'table', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr', + 'track', + 'u', + 'ul', + 'video', +] as const; + +export const AllowedAttributes = [ + 'accept', + 'accept-charset', + 'accesskey', + 'align', + 'allow', + 'allowfullscreen', + 'alt', + 'as', + 'async', + 'autocapitalize', + 'autoplay', + 'charset', + 'class', + 'cols', + 'colspan', + 'controls', + 'crossorigin', + 'data', + 'data-*', + 'datetime', + 'decoding', + 'default', + 'dir', + 'download', + 'headers', + 'hidden', + 'high', + 'href', + 'hreflang', + 'id', + 'ismap', + 'itemprop', + 'kind', + 'lang', + 'language', + 'loop', + 'low', + 'media', + 'muted', + 'optimum', + 'ping', + 'playsinline', + 'poster', + 'preload', + 'referrerpolicy', + 'rel', + 'reversed', + 'role', + 'rowspan', + 'sandbox', + 'scope', + 'shape', + 'size', + 'sizes', + 'slot', + 'span', + 'spellcheck', + 'src', + 'srcdoc', + 'srclang', + 'srcset', + 'start', + 'style', + 'target', + 'title', + 'translate', + 'usemap', + 'wrap', + 'aspect-ratio', +] as const; + +export type AllowedTagsInCustomRenderer = (typeof AllowedTags)[number]; +export type AllowedAttributesInCustomRenderer = (typeof AllowedAttributes)[number]; + +export const cleanHtml = (dirty: string): string => { + return sanitizeHtml(dirty, { + allowedTags: [ ...AllowedTags ], + allowedAttributes: { + '*': [ ...AllowedAttributes ], + input: [ 'type', 'disabled', 'checked' ] + }, + transformTags: { + input: ((tagName, attribs) => { + // Only allow input if it's a checkbox and disabled + if (attribs.type === 'checkbox' && attribs.disabled != null) { + return { + tagName, + attribs: { + type: 'checkbox', + disabled: 'disabled', + checked: attribs.checked + } + }; + } + // For all other inputs, remove the tag + return { + tagName: '', + attribs: {} + }; + }) as sanitizeHtml.Transformer + } + }); +}; diff --git a/vendor/mynah-ui/src/helper/serialize-chat.ts b/vendor/mynah-ui/src/helper/serialize-chat.ts new file mode 100644 index 00000000..1f3fa50e --- /dev/null +++ b/vendor/mynah-ui/src/helper/serialize-chat.ts @@ -0,0 +1,78 @@ +import { ChatItemCard } from '../components/chat-item/chat-item-card'; +import { MynahUITabsStore } from './tabs-store'; +import { ChatItem } from '../static'; + +/** + * Serialize all (non-empty) chat messages in a tab to a markdown string + * @param tabId Corresponding tab ID. + * @returns The bodies of chat cards in markdown format, separated by \n\n---\n\n + */ +export const serializeMarkdown = (tabId: string): string => { + return (MynahUITabsStore.getInstance().getTabDataStore(tabId).getValue('chatItems') as ChatItem[]).map(chatItem => chatItem.body ?? '').filter(chatItem => chatItem.trim() !== '').join('\n\n---\n\n') ?? ''; +}; + +/** + * Serialize all (non-empty) chat messages in a tab to an HTML string + * @param tabId Corresponding tab ID. + * @returns The bodies of chat cards in HTML format + */ +export const serializeHtml = (tabId: string): string => { + const chatItemCardDivs = (MynahUITabsStore.getInstance().getTabDataStore(tabId).getValue('chatItems') as ChatItem[]).filter((chatItem): chatItem is ChatItem => + chatItem?.body != null && chatItem.body.trim() !== '' + ).map(chatItem => new ChatItemCard({ + chatItem: { + type: chatItem.type, + body: chatItem.body, + messageId: chatItem.messageId, + status: chatItem.status, + icon: chatItem.icon + }, + tabId, + }).render.outerHTML).join('\n'); + + // Get all relevant styles from the document + const styleSheets = Array.from(document.styleSheets); + const relevantStyles = styleSheets + .map(sheet => { + try { + return Array.from(sheet.cssRules) + .map(rule => { + let ruleText = rule.cssText; + + if (rule instanceof CSSStyleRule) { + if (rule.selectorText === '.mynah-chat-wrapper') { + ruleText = `.mynah-chat-wrapper { visibility: visible !important; position: relative !important; left: initial !important; opacity: 1 !important; ${rule.style.cssText} }`; + } + if (rule.selectorText.includes('.mynah-chat-item-card')) { + ruleText = rule.cssText.replace('opacity: 0', 'opacity: 1').replace('transform: translate3d(0px, min(30%, 10vh), 0px) scale(0.99)', 'transform: none'); + } + if (rule.selectorText.includes('.mynah-syntax-highlighter-copy-buttons')) { + ruleText = rule.cssText.replace('display: flex', 'display: none'); + } + } + + return ruleText; + }) + .join('\n'); + } catch (e) { + console.warn('Could not read stylesheet rules', e); + return ''; + } + }) + .join('\n'); + + return ` + + + + + +
    +
    ${chatItemCardDivs ?? ''}
    +
    + + + `; +}; diff --git a/vendor/mynah-ui/src/helper/store.ts b/vendor/mynah-ui/src/helper/store.ts new file mode 100644 index 00000000..e6edae95 --- /dev/null +++ b/vendor/mynah-ui/src/helper/store.ts @@ -0,0 +1,164 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable @typescript-eslint/no-dynamic-delete */ +import { MynahEventNames, MynahUIDataModel } from '../static'; +import { Config } from './config'; +import { MynahUIGlobalEvents } from './events'; +import { generateUID } from './guid'; +import clone from 'just-clone'; + +const PrimitiveObjectTypes = [ 'string', 'number', 'boolean' ]; +const emptyDataModelObject: Required = { + tabTitle: '', + tabIcon: null, + pinned: false, + tabBackground: false, + loadingChat: false, + tabCloseConfirmationCloseButton: null, + tabCloseConfirmationKeepButton: null, + tabCloseConfirmationMessage: null, + cancelButtonWhenLoading: true, + showChatAvatars: false, + quickActionCommands: [], + quickActionCommandsHeader: {}, + contextCommands: [], + promptInputPlaceholder: '', + promptTopBarContextItems: [], + promptTopBarTitle: '', + promptTopBarButton: null, + promptInputText: '', + promptInputLabel: '', + promptInputVisible: true, + promptInputInfo: '', + promptInputStickyCard: null, + promptInputDisabledState: false, + promptInputProgress: null, + promptInputOptions: [], + promptInputButtons: [], + chatItems: [], + selectedCodeSnippet: '', + tabBarButtons: [], + compactMode: false, + tabHeaderDetails: null, + tabMetadata: {}, + customContextCommand: [] +}; +const dataModelKeys = Object.keys(emptyDataModelObject); +export class EmptyMynahUIDataModel { + data: Required; + constructor (defaults?: MynahUIDataModel | null) { + this.data = { + ...emptyDataModelObject, + tabTitle: Config.getInstance().config.texts.mainTitle, + ...defaults + }; + } +} +export class MynahUIDataStore { + private readonly subscriptions: Record void>>; + private readonly tabId: string; + private store: Required = (new EmptyMynahUIDataModel()).data; + private defaults: MynahUIDataModel | null = null; + + constructor (tabId: string, initialData?: MynahUIDataModel) { + this.tabId = tabId; + this.store = Object.assign(this.store, initialData); + this.subscriptions = Object.create({}); + (Object.keys(this.store) as Array).forEach((storeKey) => { + if (dataModelKeys.includes(storeKey)) { + Object.assign(this.subscriptions, { [storeKey]: {} }); + } + }); + } + + /** + * Sets the defaults to use while clearing the store + * @param defaults partial set of MynahUIDataModel for defaults + */ + public setDefaults = (defaults: MynahUIDataModel | null): void => { + this.defaults = defaults; + if (this.defaults != null) { + (Object.keys(this.defaults) as Array).forEach((storeKey) => { + if (!dataModelKeys.includes(storeKey)) { + delete this.defaults?.[storeKey]; + } + }); + } + }; + + /** + * Get the defaults to use while generating an empty store data + */ + public getDefaults = (): MynahUIDataModel | null => this.defaults; + + /** + * Get the current store data + */ + public getStore = (): MynahUIDataModel | null => this.store; + + /** + * Subscribe to value changes of a specific item in data store + * @param storeKey One of the keys in MynahUIDataModel + * @param handler function will be called when value of the given key is updated in store with new and old values + * @returns subscriptionId which needed to unsubscribe + */ + public subscribe = (storeKey: keyof MynahUIDataModel, handler: (newValue: any, oldValue?: any) => void): string => { + const subscriptionId: string = generateUID(); + this.subscriptions[storeKey][subscriptionId] = handler; + return subscriptionId; + }; + + /** + * Unsubscribe from changes of a specific item in data store + * @param storeKey One of the keys in MynahUIDataModel + * @param subscriptionId subscriptionId which is returned from subscribe function + */ + public unsubscribe = (storeKey: keyof MynahUIDataModel, subscriptionId: string): void => { + if (this.subscriptions[storeKey]?.[subscriptionId] !== undefined) { + delete this.subscriptions[storeKey][subscriptionId]; + } + }; + + /** + * Returns current value of an item in data store + * @param storeKey One of the keys in MynahUIDataModel + * @returns value of the given key in data store + */ + public getValue = (storeKey: keyof MynahUIDataModel): any => clone(this.store[storeKey] as object); + + /** + * Returns current value of an item in data store + * @param storeKey One of the keys in MynahUIDataModel + * @returns value of the given key in data store + */ + public getDefaultValue = (storeKey: keyof MynahUIDataModel): any => (new EmptyMynahUIDataModel(this.defaults)).data[storeKey]; + + /** + * Updates the store and informs the subscribers. + * @param data A full or partial set of store data model with values. + */ + public updateStore = (data: MynahUIDataModel, skipSubscribers?: boolean): void => { + if (skipSubscribers !== true) { + (Object.keys(data) as Array).forEach(storeKey => { + if (dataModelKeys.includes(storeKey)) { + Object.keys(this.subscriptions[storeKey]).forEach((subscriptionId: string) => { + if (!PrimitiveObjectTypes.includes(typeof data[storeKey]) || data[storeKey] !== this.store[storeKey]) { + this.subscriptions[storeKey][subscriptionId](data[storeKey], this.store[storeKey]); + } + }); + } + }); + } + this.store = Object.assign(clone(this.store), data); + }; + + /** + * Clears store data and informs all the subscribers + */ + public resetStore = (): void => { + this.updateStore((new EmptyMynahUIDataModel(clone(this.defaults as object))).data); + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.RESET_STORE, { tabId: this.tabId }); + }; +} diff --git a/vendor/mynah-ui/src/helper/style-loader.ts b/vendor/mynah-ui/src/helper/style-loader.ts new file mode 100644 index 00000000..67c22bb0 --- /dev/null +++ b/vendor/mynah-ui/src/helper/style-loader.ts @@ -0,0 +1,40 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +export class StyleLoader { + private readonly loadStyles: boolean; + private static instance: StyleLoader | undefined; + private constructor (loadStyles: boolean) { + this.loadStyles = loadStyles; + } + + public load = async (stylePath: string): Promise => { + if (this.loadStyles) { + try { + // Create a require context for all files in the styles directory WITH subdirectories + const context = require.context('../styles/', true, /\.scss$/); + + // Normalize the path to ensure it starts with './' + const normalizedPath = stylePath.startsWith('./') ? stylePath : `./${stylePath}`; + + // Use the context to import the file + await context(normalizedPath); + } catch (error) { + } + } + }; + + public static getInstance (loadStyles?: boolean): StyleLoader { + if (StyleLoader.instance === undefined) { + StyleLoader.instance = new StyleLoader(loadStyles ?? true); + } + + return StyleLoader.instance; + } + + public destroy = (): void => { + StyleLoader.instance = undefined; + }; +} diff --git a/vendor/mynah-ui/src/helper/tabs-store.ts b/vendor/mynah-ui/src/helper/tabs-store.ts new file mode 100644 index 00000000..317ee65f --- /dev/null +++ b/vendor/mynah-ui/src/helper/tabs-store.ts @@ -0,0 +1,257 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* eslint-disable @typescript-eslint/no-dynamic-delete */ +import clone from 'just-clone'; +import { MynahUIDataModel, MynahUITabStoreModel, MynahUITabStoreTab } from '../static'; +import { Config } from './config'; +import { generateUID } from './guid'; +import { MynahUIDataStore } from './store'; + +interface TabStoreSubscription { + 'add': Record void>; + 'remove': Record void>; + 'update': Record void>; + 'beforeTabChange': Record void>; + 'selectedTabChange': Record void>; +} +export class EmptyMynahUITabsStoreModel { + data: Required; + constructor () { + const guid = generateUID(); + this.data = { + [guid]: { + isSelected: true, + store: {}, + } + }; + } +} +export class MynahUITabsStore { + private static instance: MynahUITabsStore | undefined; + private readonly subscriptions: TabStoreSubscription = { + add: {}, + remove: {}, + update: {}, + beforeTabChange: {}, + selectedTabChange: {} + }; + + private readonly tabsStore: Required = {}; + private readonly tabsDataStore: Record = {}; + private tabDefaults: MynahUITabStoreTab = {}; + + private constructor (initialData?: MynahUITabStoreModel, defaults?: MynahUITabStoreTab) { + this.tabsStore = Object.assign(this.tabsStore, initialData); + if (defaults != null) { + this.tabDefaults = defaults; + } + if ((initialData != null) && Object.keys(initialData).length > 0) { + Object.keys(initialData).forEach((tabId: string) => { + this.tabsDataStore[tabId] = new MynahUIDataStore(tabId, initialData[tabId].store ?? {}); + }); + } + } + + private readonly deselectAllTabs = (): void => { + Object.keys(this.tabsStore).forEach(tabId => { + this.tabsStore[tabId].isSelected = false; + }); + }; + + public readonly addTab = (tabData?: MynahUITabStoreTab): string | undefined => { + if (Object.keys(this.tabsStore).length < Config.getInstance().config.maxTabs) { + const tabId = generateUID(); + this.deselectAllTabs(); + this.tabsStore[tabId] = { ...this.tabDefaults, ...tabData, isSelected: true }; + this.tabsDataStore[tabId] = new MynahUIDataStore(tabId, this.tabsStore[tabId].store ?? {}); + this.informSubscribers('add', tabId, this.tabsStore[tabId]); + this.informSubscribers('selectedTabChange', tabId, this.tabsStore[tabId]); + return tabId; + } + }; + + public readonly removeTab = (tabId: string): string => { + const wasSelected = this.tabsStore[tabId].isSelected ?? false; + let newSelectedTab: MynahUITabStoreTab | undefined; + delete this.tabsStore[tabId]; + this.tabsDataStore[tabId].resetStore(); + delete this.tabsDataStore[tabId]; + if (wasSelected) { + const tabIds = Object.keys(this.tabsStore); + if (tabIds.length > 0) { + this.deselectAllTabs(); + this.selectTab(tabIds[tabIds.length - 1]); + newSelectedTab = this.tabsStore[this.getSelectedTabId()]; + } + } + this.informSubscribers('remove', tabId, newSelectedTab); + return tabId; + }; + + public readonly selectTab = (tabId: string): void => { + this.informSubscribers('beforeTabChange', tabId, this.tabsStore[tabId]); + this.deselectAllTabs(); + this.tabsStore[tabId].isSelected = true; + this.informSubscribers('selectedTabChange', tabId, this.tabsStore[tabId]); + }; + + /** + * Updates the store and informs the subscribers. + * @param data A full or partial set of store data model with values. + */ + public updateTab = (tabId: string, tabData?: Partial, skipSubscribers?: boolean): void => { + if (this.tabsStore[tabId] != null) { + if (tabData?.isSelected === true && this.getSelectedTabId() !== tabId) { + this.selectTab(tabId); + } + this.tabsStore[tabId] = { ...this.tabsStore[tabId], ...tabData }; + if (tabData?.store != null) { + if (this.tabsDataStore[tabId] === undefined) { + this.tabsDataStore[tabId] = new MynahUIDataStore(tabId); + } + this.tabsDataStore[tabId].updateStore(tabData?.store); + } + if (skipSubscribers !== true) { + this.informSubscribers('update', tabId, this.tabsStore[tabId]); + } + } + }; + + public static getInstance = (initialData?: MynahUITabStoreModel, defaults?: MynahUITabStoreTab): MynahUITabsStore => { + if (MynahUITabsStore.instance === undefined) { + MynahUITabsStore.instance = new MynahUITabsStore(initialData, defaults); + } + + return MynahUITabsStore.instance; + }; + + /** + * Subscribe to changes of the tabsStore + * @param handler function will be called when tabs changed + * @returns subscriptionId which needed to unsubscribe + */ + public addListener = (eventName: keyof TabStoreSubscription, handler: (tabId: string, tabData?: MynahUITabStoreTab) => void): string => { + const subscriptionId: string = generateUID(); + this.subscriptions[eventName][subscriptionId] = handler; + return subscriptionId; + }; + + /** + * Subscribe to changes of the tabs' data store + * @param handler function will be called when tabs changed + * @returns subscriptionId which needed to unsubscribe + */ + public addListenerToDataStore = (tabId: string, storeKey: keyof MynahUIDataModel, handler: (newValue: any, oldValue?: any) => void): string | null => { + if (this.tabsDataStore[tabId] !== undefined) { + return this.tabsDataStore[tabId].subscribe(storeKey, handler); + } + return null; + }; + + /** + * Unsubscribe from changes of the tabs' data store + * @param handler function will be called when tabs changed + * @returns subscriptionId which needed to unsubscribe + */ + public removeListenerFromDataStore = (tabId: string, subscriptionId: string, storeKey: keyof MynahUIDataModel): void => { + if (this.tabsDataStore[tabId] !== undefined) { + this.tabsDataStore[tabId].unsubscribe(storeKey, subscriptionId); + } + }; + + /** + * Unsubscribe from changes of the tabs store + * @param subscriptionId subscriptionId which is returned from subscribe function + */ + public removeListener = (eventName: keyof TabStoreSubscription, subscriptionId: string): void => { + if (this.subscriptions[eventName][subscriptionId] !== undefined) { + delete this.subscriptions[eventName][subscriptionId]; + } + }; + + private readonly informSubscribers = (eventName: keyof TabStoreSubscription, tabId: string, tabData?: MynahUITabStoreTab): void => { + const subscriberKeys = Object.keys(this.subscriptions[eventName]); + subscriberKeys.forEach(subscriberKey => { + this.subscriptions[eventName][subscriberKey](tabId, tabData); + }); + }; + + /** + * Returns the tab + * @param tabId Tab Id + * @returns info of the tab + */ + public getTab = (tabId: string): MynahUITabStoreTab | null => this.tabsStore[tabId] ?? null; + + /** + * Returns the tab + * @param tabId Tab Id + * @returns info of the tab + */ + public getAllTabs = (): MynahUITabStoreModel => { + const clonedTabs = clone(this.tabsStore) as MynahUITabStoreModel; + Object.keys(clonedTabs).forEach(tabId => { + clonedTabs[tabId].store = clone(this.getTabDataStore(tabId).getStore() as object) ?? {}; + }); + return clonedTabs; + }; + + /** + * Returns the data store of the tab + * @param tabId Tab Id + * @returns data of the tab + */ + public getTabDataStore = (tabId: string): MynahUIDataStore => this.tabsDataStore[tabId]; + + /** + * Returns the data of the tab + * @param tabId Tab Id + * @returns data of the tab + */ + public getSelectedTabId = (): string => { + const tabIds = Object.keys(this.tabsStore); + return tabIds.find(tabId => this.tabsStore[tabId].isSelected === true) ?? ''; + }; + + /** + * Clears store data and informs all the subscribers + */ + public removeAllTabs = (): void => { + Object.keys(this.tabsStore).forEach(tabId => { + this.removeTab(tabId); + }); + }; + + /** + * Get all tabs length + * @returns tabs length + */ + public tabsLength = (): number => Object.keys(this.tabsStore).length; + + /** + * Updates defaults of the tab store + * @param defaults MynahUITabStoreTab + */ + public updateTabDefaults = (defaults: MynahUITabStoreTab): void => { + this.tabDefaults = { + store: { + ...this.tabDefaults.store, + ...defaults.store + } + }; + }; + + /** + * Updates defaults of the tab store + * @param defaults MynahUITabStoreTab + */ + public getTabDefaults = (): MynahUITabStoreTab => { + return this.tabDefaults; + }; + + public destroy = (): void => { + MynahUITabsStore.instance = undefined; + }; +} diff --git a/vendor/mynah-ui/src/helper/test-ids.ts b/vendor/mynah-ui/src/helper/test-ids.ts new file mode 100644 index 00000000..f399b8ef --- /dev/null +++ b/vendor/mynah-ui/src/helper/test-ids.ts @@ -0,0 +1,179 @@ +export default { + selector: 'data-testid', + prompt: { + wrapper: 'prompt-input-wrapper', + attachmentWrapper: 'prompt-input-attachment-wrapper', + attachment: 'prompt-input-attachment', + options: 'prompt-input-options', + attachmentRemove: 'prompt-input-attachment-remove-button', + send: 'prompt-input-send-button', + input: 'prompt-input-textarea', + inputWrapper: 'prompt-input-input-wrapper', + remainingCharsIndicator: 'prompt-input-remaining-chars-indicator', + contextTooltip: 'prompt-input-context-tooltip', + selectedCommand: 'prompt-input-selected-command', + quickPicksWrapper: 'prompt-input-quick-picks-wrapper', + quickPicksGroup: 'prompt-input-quick-picks-group', + quickPicksGroupTitle: 'prompt-input-quick-picks-group-title', + quickPickItem: 'prompt-input-quick-pick-item', + footerInfo: 'prompt-input-footer-info', + footerInfoBody: 'prompt-input-footer-info-body', + stickyCard: 'prompt-input-sticky-card', + progress: 'prompt-input-progress-wrapper', + label: 'prompt-input-label', + topBar: 'prompt-input-top-bar', + topBarButton: 'prompt-input-top-bar-button', + topBarContextPill: 'prompt-input-top-bar-context-pill', + topBarContextTooltip: 'prompt-input-top-bar-context-tooltip', + topBarOverflowPill: 'prompt-input-top-bar-overflow-pill', + topBarOverflowOverlay: 'prompt-input-top-bar-overflow-overlay', + topBarActionOverlay: 'prompt-input-top-bar-action-overlay' + + }, + chat: { + wrapper: 'chat-wrapper', + chatItemsContainer: 'chat-chat-items-container', + conversationContainer: 'chat-chat-items-conversation-container', + middleBlockWrapper: 'chat-middle-block-wrapper', + stopButton: 'chat-middle-block-stop-button', + header: 'chat-wrapper-header-details', + moreContentIndicator: 'chat-wrapper-more-content-available-indicator', + moreContentIndicatorButton: 'chat-wrapper-more-content-available-indicator-button', + }, + chatItem: { + type: { + any: 'chat-item', + answer: 'chat-item-answer', + answerStream: 'chat-item-answer-stream', + prompt: 'chat-item-prompt', + aiPrompt: 'chat-item-ai-prompt', + systemPrompt: 'chat-item-system-prompt', + }, + moreContentIndicator: 'chat-item-card-more-content-indicator', + card: 'chat-item-card', + cardBody: 'chat-item-card-body', + buttons: { + wrapper: 'chat-item-buttons-wrapper', + button: 'chat-item-action-button', + }, + dismissButton: 'chat-item-dismiss-button', + chatItemFollowup: { + optionsWrapper: 'chat-item-followup-options-wrapper', + optionButton: 'chat-item-followup-option', + title: 'chat-item-followup-title', + wrapper: 'chat-item-followup-wrapper', + }, + syntaxHighlighter: { + wrapper: 'chat-item-syntax-highlighter-wrapper', + codeBlock: 'chat-item-syntax-highlighter-code-block', + lineNumbers: 'chat-item-syntax-highlighter-line-numbers', + language: 'chat-item-syntax-highlighter-language', + buttonsWrapper: 'chat-item-syntax-highlighter-buttons-wrapper', + button: 'chat-item-syntax-highlighter-button', + }, + chatItemForm: { + wrapper: 'chat-item-form-wrapper', + title: 'chat-item-form-title', + description: 'chat-item-form-description', + itemSelectWrapper: 'chat-item-form-item-select-wrapper', + itemSelect: 'chat-item-form-item-select', + itemRadioWrapper: 'chat-item-form-item-radio-wrapper', + itemRadio: 'chat-item-form-item-radio', + itemInput: 'chat-item-form-item-text-input', + itemList: 'chat-item-form-item-list', + itemStarsWrapper: 'chat-item-form-item-stars-wrapper', + itemStars: 'chat-item-form-item-stars', + itemTextArea: 'chat-item-form-item-textarea', + itemToggleWrapper: 'chat-item-form-item-toggle-wrapper', + itemToggleOption: 'chat-item-form-item-toggle-option', + itemSwitch: 'chat-item-form-item-switch', + }, + vote: { + wrapper: 'chat-item-vote-wrapper', + upvote: 'chat-item-upvote', + upvoteLabel: 'chat-item-upvote-label', + downvote: 'chat-item-downvote', + downvoteLabel: 'chat-item-downvote-label', + reportButton: 'chat-item-vote-report', + thanks: 'chat-item-vote-thanks' + }, + relatedLinks: { + showMore: 'chat-item-related-links-show-more', + wrapper: 'chat-item-related-links-wrapper', + title: 'chat-item-related-links-title', + linkWrapper: 'chat-item-related-link-wrapper', + link: 'chat-item-related-link', + linkPreviewOverlay: 'chat-item-related-link-preview-overlay', + linkPreviewOverlayCard: 'chat-item-related-link-preview-overlay-card' + }, + fileTree: { + wrapper: 'chat-item-file-tree-wrapper', + title: 'chat-item-file-tree-title', + license: 'chat-item-file-tree-license', + folder: 'chat-item-file-tree-folder', + file: 'chat-item-file-tree-file', + fileAction: 'chat-item-file-tree-file-action', + fileTooltipWrapper: 'chat-item-file-tree-file-tooltip-wrapper', + }, + tabbedCard: { + tabs: 'chat-item-tabbed-card-tabs' + } + }, + feedbackForm: { + optionsSelectWrapper: 'feedback-form-options-select-wrapper', + optionsSelect: 'feedback-form-options-select', + comment: 'feedback-form-comment-text-area', + cancelButton: 'feedback-form-cancel-button', + submitButton: 'feedback-form-submit-button', + }, + sheet: { + wrapper: 'sheet-wrapper', + header: 'sheet-header', + title: 'sheet-title', + description: 'sheet-description', + closeButton: 'sheet-close-button', + }, + detailedList: { + action: 'detailed-list-action', + actionMenu: 'detailed-list-action-menu', + status: 'detailed-list-status' + }, + tabBar: { + wrapper: 'tab-bar-wrapper', + buttonsWrapper: 'tab-bar-buttons-wrapper', + button: 'tab-bar-button', + menuButton: 'tab-bar-menu-button', + menuOption: 'tab-bar-menu-option', + tabsWrapper: 'tab-bar-tabs', + tabOptionWrapper: 'tab-bar-tabs-option-wrapper', + tabOption: 'tab-bar-tabs-option', + tabOptionLabel: 'tab-bar-tabs-option-label', + tabOptionCloseButton: 'tab-bar-tabs-option-close-button', + tabAddButton: 'tab-bar-tab-add-button', + maxTabsReachedOverlay: 'tab-bar-max-tabs-reached-overlay', + tabCloseConfirmationOverlay: 'tab-bar-tab-close-confirmation-overlay', + tabCloseConfirmationBody: 'tab-bar-tab-close-confirmation-body', + tabCloseConfirmationCancelButton: 'tab-bar-tab-close-confirmation-cancel-button', + tabCloseConfirmationAcceptButton: 'tab-bar-tab-close-confirmation-accept-button' + }, + noTabs: { + wrapper: 'no-tabs-wrapper', + newTabButton: 'no-tabs-new-tab-button' + }, + notification: { + wrapper: 'notification-wrapper', + title: 'notification-title', + content: 'notification-content', + }, + dropdownList: { + wrapper: 'dropdown-list-wrapper', + button: 'dropdown-list-button', + portal: 'dropdown-list-portal', + content: 'dropdown-list-content', + title: 'dropdown-list-title', + description: 'dropdown-list-description', + option: 'dropdown-list-option', + optionLabel: 'dropdown-list-option-label', + checkIcon: 'dropdown-list-check-icon' + } +}; diff --git a/vendor/mynah-ui/src/helper/url.ts b/vendor/mynah-ui/src/helper/url.ts new file mode 100644 index 00000000..704a5789 --- /dev/null +++ b/vendor/mynah-ui/src/helper/url.ts @@ -0,0 +1,9 @@ +export const getOrigin = (site: string): string => { + let result = ''; + try { + result = new URL(site).origin; + } catch (err) { + result = 'unknown'; + } + return result; +}; diff --git a/vendor/mynah-ui/src/helper/validator.ts b/vendor/mynah-ui/src/helper/validator.ts new file mode 100644 index 00000000..be8617d7 --- /dev/null +++ b/vendor/mynah-ui/src/helper/validator.ts @@ -0,0 +1,56 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { DomBuilder, ExtendedHTMLElement } from '../main'; +import { ValidationPattern } from '../static'; + +export const isTextualFormItemValid = (value: string, validationPatterns: { + operator?: 'and' | 'or'; + genericValidationErrorMessage?: string; + patterns: ValidationPattern[]; +}, mandatory?: boolean): { + isValid: boolean; + validationErrors: string[]; +} => { + let isValid = true; + let validationErrors: string[] = []; + if (validationPatterns != null) { + isValid = validationPatterns.patterns.reduce((prevValidation, currentPattern): boolean => { + const isCurrentPatternValid = (value.match(currentPattern.pattern) != null); + if (!isCurrentPatternValid && currentPattern.errorMessage != null) { + validationErrors.push(currentPattern.errorMessage); + } + if (validationPatterns.operator === 'and') { + return prevValidation && isCurrentPatternValid; + } + return prevValidation || isCurrentPatternValid; + }, validationPatterns.operator === 'and'); + } + // Don't invalidate if the field is empty and non mandatory + isValid = isValid || ((value === undefined || value.trim() === '') && (mandatory !== true)); + if (isValid) { + validationErrors = []; + } else if (validationErrors.length === 0 && validationPatterns.genericValidationErrorMessage != null) { + validationErrors.push(validationPatterns.genericValidationErrorMessage); + } + return { isValid, validationErrors }; +}; + +export const isMandatoryItemValid = (value: string): boolean => value !== undefined && value.trim() !== ''; + +export const checkTextElementValidation = (inputElement: ExtendedHTMLElement, validationPatterns: { + operator?: 'and' | 'or'; + patterns: ValidationPattern[]; +} | undefined, validationErrorBlock: ExtendedHTMLElement, readyToValidate: boolean, mandatory?: boolean): void => { + const { isValid, validationErrors } = isTextualFormItemValid(inputElement.value, validationPatterns ?? { patterns: [] }, mandatory); + if (readyToValidate && validationErrors.length > 0 && !isValid) { + inputElement.addClass('validation-error'); + validationErrorBlock.update({ children: validationErrors.map(message => DomBuilder.getInstance().build({ type: 'span', children: [ message ] })) }); + } else { + readyToValidate = false; + validationErrorBlock.clear(); + inputElement.removeClass('validation-error'); + } +}; diff --git a/vendor/mynah-ui/src/main.ts b/vendor/mynah-ui/src/main.ts new file mode 100644 index 00000000..9de8144f --- /dev/null +++ b/vendor/mynah-ui/src/main.ts @@ -0,0 +1,1235 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { Notification } from './components/notification'; +import { DomBuilder, ExtendedHTMLElement } from './helper/dom'; +import { + MynahPortalNames, + RelevancyVoteType, + FeedbackPayload, + MynahUIDataModel, + MynahEventNames, + NotificationType, + ChatItem, + ChatItemAction, + ChatPrompt, + MynahUITabStoreModel, + MynahUITabStoreTab, + ConfigModel, + ReferenceTrackerInformation, + CodeSelectionType, + Engagement, + ChatItemFormItem, + ChatItemButton, + ChatItemType, + CardRenderDetails, + PromptAttachmentType, + QuickActionCommand, + DetailedList, + TreeNodeDetails, + Action, + DropdownListOption, +} from './static'; +import { cancelEvent, MynahUIGlobalEvents } from './helper/events'; +import { Tabs } from './components/navigation-tabs'; +import { ChatWrapper } from './components/chat-item/chat-wrapper'; +import { FeedbackForm } from './components/feedback-form/feedback-form'; +import { MynahUITabsStore } from './helper/tabs-store'; +import { Config } from './helper/config'; +import { generateUID } from './helper/guid'; +import { NoTabs } from './components/no-tabs'; +import { copyToClipboard } from './helper/chat-item'; +import { serializeHtml, serializeMarkdown } from './helper/serialize-chat'; +import { Sheet } from './components/sheet'; +import { DetailedListSheet, DetailedListSheetProps } from './components/detailed-list/detailed-list-sheet'; +import { configureMarked, parseMarkdown } from './helper/marked'; +import { MynahUIDataStore } from './helper/store'; +import { StyleLoader } from './helper/style-loader'; +import { Icon } from './components/icon'; +import { Button } from './components/button'; +import { TopBarButtonOverlayProps } from './components/chat-item/prompt-input/prompt-top-bar/top-bar-button'; + +export { generateUID } from './helper/guid'; +export { + ChatItemBodyRenderer, +} from './helper/dom'; +export { + AllowedAttributesInCustomRenderer, + AllowedTagsInCustomRenderer +} from './helper/sanitize'; +export * from './static'; +export { + ToggleOption +} from './components/tabs'; +export { + MynahIcons, + MynahIconsType +} from './components/icon'; +export { + DomBuilder, + DomBuilderObject, + ExtendedHTMLElement, +} from './helper/dom'; +export { + ButtonProps, + ButtonAbstract +} from './components/button'; +export { + RadioGroupProps, + RadioGroupAbstract +} from './components/form-items/radio-group'; +export { + SelectProps, + SelectAbstract +} from './components/form-items/select'; +export { + TextInputProps, + TextInputAbstract +} from './components/form-items/text-input'; +export { + TextAreaProps, + TextAreaAbstract +} from './components/form-items/text-area'; +export { + ChatItemCardContent, + ChatItemCardContentProps +} from './components/chat-item/chat-item-card-content'; +export { default as MynahUITestIds } from './helper/test-ids'; + +export interface MynahUIProps { + rootSelector?: string; + loadStyles?: boolean; + defaults?: MynahUITabStoreTab; + splashScreenInitialStatus?: { + visible: boolean; + text?: string; + actions?: Action[]; + }; + tabs?: MynahUITabStoreModel; + config?: Partial; + onShowMoreWebResultsClick?: ( + tabId: string, + messageId: string, + eventId?: string) => void; + onReady?: () => void; + onFocusStateChanged?: (focusState: boolean) => void; + onVote?: ( + tabId: string, + messageId: string, + vote: RelevancyVoteType, + eventId?: string) => void; + onStopChatResponse?: ( + tabId: string, + eventId?: string) => void; + onResetStore?: (tabId: string) => void; + onChatPrompt?: ( + tabId: string, + prompt: ChatPrompt, + eventId?: string) => void; + onChatPromptProgressActionButtonClicked?: ( + tabId: string, + action: { + id: string; + text?: string; + }, + eventId?: string) => void; + onFollowUpClicked?: ( + tabId: string, + messageId: string, + followUp: ChatItemAction, + eventId?: string) => void; + onInBodyButtonClicked?: ( + tabId: string, + messageId: string, + action: { + id: string; + text?: string; + formItemValues?: Record; + }, + eventId?: string) => void; + onTabbedContentTabChange?: ( + tabId: string, + messageId: string, + contentTabId: string, + eventId?: string) => void; + onTabChange?: ( + tabId: string, + eventId?: string) => void; + onTabAdd?: ( + tabId: string, + eventId?: string) => void; + onContextSelected?: ( + contextItem: QuickActionCommand, + tabId: string, + eventId?: string + ) => boolean; + onTabRemove?: ( + tabId: string, + eventId?: string) => void; + onSearchShortcut?: ( + tabId: string, + eventId?: string) => void; + /** + * @param tabId tabId which the close button triggered + * @returns boolean -> If you want to close the tab immediately send true + */ + onBeforeTabRemove?: ( + tabId: string, + eventId?: string) => boolean; + onChatItemEngagement?: ( + tabId: string, + messageId: string, + engagement: Engagement) => void; + onCodeBlockActionClicked?: ( + tabId: string, + messageId: string, + actionId: string, + data?: string, + code?: string, + type?: CodeSelectionType, + referenceTrackerInformation?: ReferenceTrackerInformation[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number,) => void; + /** + * @deprecated since version 4.14.0. It will be only used for keyboard, context menu copy actions, not for button actions after version 5.x.x. Use {@link onCodeBlockActionClicked} instead + */ + onCopyCodeToClipboard?: ( + tabId: string, + messageId: string, + code?: string, + type?: CodeSelectionType, + referenceTrackerInformation?: ReferenceTrackerInformation[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number, + data?: any) => void; + /** + * @deprecated since version 4.14.0. Will be dropped after version 5.x.x. Use {@link onCodeBlockActionClicked} instead + */ + onCodeInsertToCursorPosition?: ( + tabId: string, + messageId: string, + code?: string, + type?: CodeSelectionType, + referenceTrackerInformation?: ReferenceTrackerInformation[], + eventId?: string, + codeBlockIndex?: number, + totalCodeBlocks?: number, + data?: any) => void; + onSourceLinkClick?: ( + tabId: string, + messageId: string, + link: string, + mouseEvent?: MouseEvent, + eventId?: string) => void; + onLinkClick?: ( + tabId: string, + messageId: string, + link: string, + mouseEvent?: MouseEvent, + eventId?: string) => void; + onInfoLinkClick?: ( + tabId: string, + link: string, + mouseEvent?: MouseEvent, + eventId?: string) => void; + onFormLinkClick?: ( + link: string, + mouseEvent?: MouseEvent, + eventId?: string) => void; + onSendFeedback?: ( + tabId: string, + feedbackPayload: FeedbackPayload, + eventId?: string) => void; + onFormModifierEnterPress?: ( + formData: Record, + tabId: string, + eventId?: string) => void; + onFormTextualItemKeyPress?: ( + event: KeyboardEvent, + formData: Record, + itemId: string, + tabId: string, + eventId?: string) => boolean; + onFormChange?: ( + formData: Record, + isValid: boolean, + tabId: string) => void; + onCustomFormAction?: ( + tabId: string, + action: { + id: string; + text?: string; + formItemValues?: Record; + }, + eventId?: string) => void; + onDropDownOptionChange?: ( + tabId: string, + messageId: string, + value: DropdownListOption[]) => void; + onDropDownLinkClick?: ( + tabId: string, + actionId: string, + destination: string) => void; + onPromptInputOptionChange?: ( + tabId: string, + optionsValues: Record, + eventId?: string) => void; + onPromptInputButtonClick?: ( + tabId: string, + buttonId: string, + eventId?: string) => void; + onPromptTopBarItemAdded?: (tabId: string, + item: QuickActionCommand, eventId?: string) => void; + onPromptTopBarItemRemoved?: (tabId: string, + item: QuickActionCommand, eventId?: string) => void; + onPromptTopBarButtonClick?: (tabId: string, button: ChatItemButton, eventId?: string) => void; + /** + * @deprecated since version 4.6.3. Will be dropped after version 5.x.x. Use {@link onFileClick} instead + */ + onOpenDiff?: ( + tabId: string, + filePath: string, + deleted: boolean, + messageId?: string, + eventId?: string) => void; + onFileClick?: ( + tabId: string, + filePath: string, + deleted: boolean, + messageId?: string, + eventId?: string, + fileDetails?: TreeNodeDetails + ) => void; + onMessageDismiss?: ( + tabId: string, + messageId: string, + eventId?: string) => void; + onFileActionClick?: ( + tabId: string, + messageId: string, + filePath: string, + actionName: string, + eventId?: string) => void; + onTabBarButtonClick?: ( + tabId: string, + buttonId: string, + eventId?: string) => void; + onQuickCommandGroupActionClick?: ( + tabId: string, + action: { + id: string; + }, + eventId?: string) => void; + onSplashLoaderActionClick?: ( + action: Action, + eventId?: string) => void; + onOpenFileDialogClick?: ( + tabId: string, + fileType: string, + insertPosition: number + ) => void; + onFilesDropped?: ( + tabId: string, + files: FileList, + insertPosition: number + ) => void; +} + +export class MynahUI { + private readonly render: ExtendedHTMLElement; + private lastEventId: string = ''; + private readonly props: MynahUIProps; + private readonly splashLoader: ExtendedHTMLElement; + private readonly splashLoaderText: ExtendedHTMLElement; + private readonly splashLoaderActions: ExtendedHTMLElement; + private readonly tabsWrapper: ExtendedHTMLElement; + private readonly tabContentsWrapper: ExtendedHTMLElement; + private readonly feedbackForm?: FeedbackForm; + private readonly sheet?: Sheet; + private readonly chatWrappers: Record = {}; + + constructor (props: MynahUIProps) { + StyleLoader.getInstance(props.loadStyles !== false).load('styles.scss'); + configureMarked(); + + this.props = props; + Config.getInstance(props.config); + DomBuilder.getInstance(props.rootSelector); + MynahUITabsStore.getInstance(this.props.tabs, this.props.defaults); + MynahUIGlobalEvents.getInstance(); + + this.splashLoaderText = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-ui-splash-loader-text' ], + innerHTML: parseMarkdown(this.props.splashScreenInitialStatus?.text ?? '', { includeLineBreaks: true }), + }); + + this.splashLoaderActions = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-ui-splash-loader-buttons' ], + children: this.getSplashLoaderActions(this.props.splashScreenInitialStatus?.actions) + }); + + const initTabs = MynahUITabsStore.getInstance().getAllTabs(); + this.tabContentsWrapper = DomBuilder.getInstance().build({ + type: 'div', + classNames: [ 'mynah-ui-tab-contents-wrapper' ], + children: Object.keys(initTabs).slice(0, Config.getInstance().config.maxTabs).map((tabId: string) => { + this.chatWrappers[tabId] = new ChatWrapper({ + tabId, + onStopChatResponse: props.onStopChatResponse != null + ? (tabId) => { + if (props.onStopChatResponse != null) { + props.onStopChatResponse(tabId, this.getUserEventId()); + } + } + : undefined, + }); + return this.chatWrappers[tabId].render; + }) + }); + + if (props.onSendFeedback !== undefined) { + this.feedbackForm = new FeedbackForm(); + } + + this.sheet = new Sheet(); + + if (Config.getInstance().config.maxTabs > 1) { + this.tabsWrapper = new Tabs({ + onChange: (selectedTabId: string) => { + this.focusToInput(selectedTabId); + if (this.props.onTabChange !== undefined) { + this.props.onTabChange(selectedTabId, this.getUserEventId()); + } + }, + maxTabsTooltipDuration: Config.getInstance().config.maxTabsTooltipDuration, + noMoreTabsTooltip: Config.getInstance().config.noMoreTabsTooltip, + onBeforeTabRemove: (tabId): boolean => { + if (props.onBeforeTabRemove !== undefined) { + return props.onBeforeTabRemove(tabId, this.getUserEventId()); + } + return true; + } + }).render; + + this.tabsWrapper.setAttribute('selected-tab', MynahUITabsStore.getInstance().getSelectedTabId()); + } + + this.render = DomBuilder.getInstance().createPortal( + MynahPortalNames.WRAPPER, + { + type: 'div', + attributes: { + id: 'mynah-wrapper' + }, + children: [ + this.tabsWrapper ?? '', + ...(Config.getInstance().config.maxTabs > 1 ? [ new NoTabs().render ] : []), + this.tabContentsWrapper, + ] + }, + 'afterbegin' + ); + this.splashLoader = DomBuilder.getInstance().createPortal( + MynahPortalNames.LOADER, + { + type: 'div', + classNames: [ 'mynah-ui-splash-loader-wrapper' ], + children: [ + { + type: 'div', + classNames: [ 'mynah-ui-splash-loader-container' ], + children: [ + new Icon({ icon: 'progress' }).render, + this.splashLoaderText + ] + }, + this.splashLoaderActions + ] + }, + 'beforeend'); + if (this.props.splashScreenInitialStatus?.visible === true) { + this.splashLoader.addClass('visible'); + } + + MynahUITabsStore.getInstance().addListener('add', (tabId: string) => { + this.chatWrappers[tabId] = new ChatWrapper({ + tabId, + onStopChatResponse: props.onStopChatResponse != null + ? (tabId) => { + if (props.onStopChatResponse != null) { + props.onStopChatResponse(tabId, this.getUserEventId()); + } + } + : undefined, + }); + this.tabContentsWrapper.appendChild(this.chatWrappers[tabId].render); + this.focusToInput(tabId); + if (this.props.onTabAdd !== undefined) { + this.props.onTabAdd(tabId, this.getUserEventId()); + } + }); + MynahUITabsStore.getInstance().addListener('remove', (tabId: string) => { + this.chatWrappers[tabId].render.remove(); + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.chatWrappers[tabId]; + if (this.props.onTabRemove !== undefined) { + this.props.onTabRemove(tabId, this.getUserEventId()); + } + }); + + this.addGlobalListeners(); + const tabId = MynahUITabsStore.getInstance().getSelectedTabId() ?? ''; + window.addEventListener('focus', () => { + this.focusToInput(tabId); + }, false); + this.focusToInput(tabId); + if (this.props.onReady !== undefined) { + this.props.onReady(); + } + if (Config.getInstance().config.enableSearchKeyboardShortcut === true) { + document.addEventListener('keydown', (e) => { + // Check for Command+F (Mac) or Ctrl+F (Windows/Linux) + if ((e.metaKey || e.ctrlKey) && e.key === 'f') { + cancelEvent(e); + // Call the search shortcut handler with the current tab ID + if (this.props.onSearchShortcut !== undefined) { + this.props.onSearchShortcut( + MynahUITabsStore.getInstance().getSelectedTabId(), + this.getUserEventId()); + } + } + }); + } + } + + private readonly getSplashLoaderActions = (actions?: Action[]): ExtendedHTMLElement[] => { + return (actions ?? []).map(action => new Button({ + onClick: () => { + this.props.onSplashLoaderActionClick?.(action, this.getUserEventId()); + }, + label: action.text, + status: action.status, + primary: action.status === 'primary', + icon: action.icon != null ? new Icon({ icon: action.icon }).render : undefined, + confirmation: action.confirmation, + disabled: action.disabled, + tooltip: action.description + }).render); + }; + + private readonly getUserEventId = (): string => { + this.lastEventId = generateUID(); + return this.lastEventId; + }; + + private readonly focusToInput = (tabId: string): void => { + if (Config.getInstance().config.autoFocus) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.TAB_FOCUS, { tabId }); + } + }; + + private readonly addGlobalListeners = (): void => { + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CHAT_PROMPT, (data: { tabId: string; prompt: ChatPrompt }) => { + if (this.props.onChatPrompt !== undefined) { + this.props.onChatPrompt(data.tabId, data.prompt, this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FOLLOW_UP_CLICKED, (data: { + tabId: string; + messageId: string; + followUpOption: ChatItemAction; + }) => { + if (this.props.onFollowUpClicked !== undefined) { + this.props.onFollowUpClicked( + data.tabId, + data.messageId, + data.followUpOption, + this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CONTEXT_SELECTED, (data: { + contextItem: QuickActionCommand; + tabId: string; + promptInputCallback: (insert: boolean) => void; + }) => { + data.promptInputCallback(this.props.onContextSelected === undefined || this.props.onContextSelected(data.contextItem, data.tabId, this.getUserEventId())); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FORM_MODIFIER_ENTER_PRESS, (data: { + formData: Record; + tabId: string; + }) => { + if (this.props.onFormModifierEnterPress !== undefined) { + this.props.onFormModifierEnterPress(data.formData, data.tabId, this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FORM_TEXTUAL_ITEM_KEYPRESS, (data: { + event: KeyboardEvent; + formData: Record; + itemId: string; + tabId: string; + callback: (disableAll?: boolean) => void; + }) => { + if (this.props.onFormTextualItemKeyPress !== undefined) { + data.callback(this.props.onFormTextualItemKeyPress(data.event, data.formData, data.itemId, data.tabId, this.getUserEventId())); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FORM_CHANGE, (data: { + formData: Record; + isValid: boolean; + tabId: string; + }) => { + if (this.props.onFormChange !== undefined) { + this.props.onFormChange(data.formData, data.isValid, data.tabId); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.BODY_ACTION_CLICKED, (data: { + tabId: string; + messageId: string; + actionId: string; + actionText?: string; + formItemValues?: Record; + }) => { + if (this.props.onInBodyButtonClicked !== undefined) { + this.props.onInBodyButtonClicked(data.tabId, data.messageId, { + id: data.actionId, + text: data.actionText, + formItemValues: data.formItemValues + }, this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.QUICK_COMMAND_GROUP_ACTION_CLICK, (data: { + tabId: string; + actionId: string; + }) => { + if (this.props.onQuickCommandGroupActionClick !== undefined) { + this.props.onQuickCommandGroupActionClick(data.tabId, { + id: data.actionId, + }, this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.TABBED_CONTENT_SWITCH, (data: { + tabId: string; + messageId: string; + contentTabId: string; + }) => { + if (this.props.onTabbedContentTabChange != null) { + this.props.onTabbedContentTabChange(data.tabId, data.messageId, data.contentTabId); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.PROMPT_PROGRESS_ACTION_CLICK, (data: { + tabId: string; + actionId: string; + actionText?: string; + }) => { + if (this.props.onChatPromptProgressActionButtonClicked !== undefined) { + this.props.onChatPromptProgressActionButtonClicked(data.tabId, { + id: data.actionId, + text: data.actionText + }, this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.SHOW_MORE_WEB_RESULTS_CLICK, (data: { messageId: string }) => { + if (this.props.onShowMoreWebResultsClick !== undefined) { + this.props.onShowMoreWebResultsClick( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.messageId, + this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FEEDBACK_SET, (feedbackData) => { + if (this.props.onSendFeedback !== undefined) { + this.props.onSendFeedback( + MynahUITabsStore.getInstance().getSelectedTabId(), + feedbackData, + this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CHAT_ITEM_ENGAGEMENT, (data: { engagement: Engagement; messageId: string }) => { + if (this.props.onChatItemEngagement !== undefined) { + this.props.onChatItemEngagement(MynahUITabsStore.getInstance().getSelectedTabId(), data.messageId, data.engagement); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CODE_BLOCK_ACTION, (data) => { + // TODO needs to be deprecated and followed through onCodeBlockActionClicked + if (data.actionId === 'insert-to-cursor') { + this.props.onCodeInsertToCursorPosition?.( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.messageId, + data.text, + data.type, + data.referenceTrackerInformation, + this.getUserEventId(), + data.codeBlockIndex, + data.totalCodeBlocks, + ); + } + // TODO needs to be deprecated and followed through onCodeBlockActionClicked + if (data.actionId === 'copy') { + copyToClipboard(data.text, (): void => { + this.props.onCopyCodeToClipboard?.( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.messageId, + data.text, + data.type, + data.referenceTrackerInformation, + this.getUserEventId(), + data.codeBlockIndex, + data.totalCodeBlocks, + ); + }); + } + + this.props.onCodeBlockActionClicked?.( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.messageId, + data.actionId, + data.data, + data.text, + data.type, + data.referenceTrackerInformation, + this.getUserEventId(), + data.codeBlockIndex, + data.totalCodeBlocks, + ); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.COPY_CODE_TO_CLIPBOARD, (data) => { + if (this.props.onCopyCodeToClipboard !== undefined) { + this.props.onCopyCodeToClipboard( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.messageId, + data.text, + data.type, + data.referenceTrackerInformation, + this.getUserEventId(), + data.codeBlockIndex, + data.totalCodeBlocks, + ); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.SOURCE_LINK_CLICK, (data) => { + if (this.props.onSourceLinkClick !== undefined) { + this.props.onSourceLinkClick( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.messageId, + data.link, + data.event, + this.getUserEventId() + ); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.LINK_CLICK, (data) => { + if (this.props.onLinkClick !== undefined) { + this.props.onLinkClick( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.messageId, + data.link, + data.event, + this.getUserEventId() + ); + } + }); + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FORM_LINK_CLICK, (data) => { + if (this.props.onFormLinkClick !== undefined) { + this.props.onFormLinkClick( + data.link, + data.event, + this.getUserEventId() + ); + } + }); + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.INFO_LINK_CLICK, (data) => { + if (this.props.onInfoLinkClick !== undefined) { + this.props.onInfoLinkClick( + MynahUITabsStore.getInstance().getSelectedTabId(), + data.link, + data.event, + this.getUserEventId() + ); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CARD_VOTE, (data) => { + if (this.props.onVote !== undefined) { + this.props.onVote( + data.tabId, + data.messageId, + data.vote, + this.getUserEventId() + ); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.RESET_STORE, (data: { tabId: string }) => { + if (this.props.onResetStore !== undefined) { + this.props.onResetStore(data.tabId); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.ROOT_FOCUS, (data: { focusState: boolean }) => { + this.props.onFocusStateChanged?.(data.focusState); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FILE_CLICK, (data) => { + if (this.props.onFileClick !== undefined) { + this.props.onFileClick( + data.tabId, + data.filePath, + data.deleted, + data.messageId, + this.getUserEventId(), + data.fileDetails); + } + + if (this.props.onOpenDiff !== undefined) { + console.warn('onOpenDiff will be deprecated after v5.x.x. Please use MynahUIProps.onFileClick instead'); + this.props.onOpenDiff( + data.tabId, + data.filePath, + data.deleted, + data.messageId, + this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CARD_DISMISS, (data) => { + this.props.onMessageDismiss?.( + data.tabId, + data.messageId, + this.getUserEventId()); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FILE_ACTION_CLICK, (data) => { + if (this.props.onFileActionClick !== undefined) { + this.props.onFileActionClick( + data.tabId, + data.messageId, + data.filePath, + data.actionName, + this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.CUSTOM_FORM_ACTION_CLICK, (data) => { + if (this.props.onCustomFormAction !== undefined) { + this.props.onCustomFormAction(data.tabId, data, this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.PROMPT_INPUT_OPTIONS_CHANGE, (data) => { + this.props.onPromptInputOptionChange?.(data.tabId, data.optionsValues, this.getUserEventId()); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.DROPDOWN_OPTION_CHANGE, (data) => { + this.props.onDropDownOptionChange?.(data.tabId, data.messageId, data.value); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.DROPDOWN_LINK_CLICK, (data) => { + this.props.onDropDownLinkClick?.(data.tabId, data.actionId, data.destination); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.TOP_BAR_ITEM_ADD, (data) => { + this.props.onPromptTopBarItemAdded?.(data.tabId, data.contextItem, this.getUserEventId()); + }); + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.TOP_BAR_ITEM_REMOVE, (data) => { + this.props.onPromptTopBarItemRemoved?.(data.tabId, data.contextItem, this.getUserEventId()); + }); + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.TOP_BAR_BUTTON_CLICK, (data) => { + this.props.onPromptTopBarButtonClick?.(data.tabId, data.button, this.getUserEventId()); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.PROMPT_INPUT_BUTTON_CLICK, (data) => { + this.props.onPromptInputButtonClick?.(data.tabId, data.buttonId, this.getUserEventId()); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.TAB_BAR_BUTTON_CLICK, (data) => { + if (this.props.onTabBarButtonClick !== undefined) { + this.props.onTabBarButtonClick(data.tabId, data.buttonId, this.getUserEventId()); + } + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.OPEN_FILE_SYSTEM, (data) => { + this.props.onOpenFileDialogClick?.(data.tabId, data.type, data.insertPosition); + }); + + MynahUIGlobalEvents.getInstance().addListener(MynahEventNames.FILES_DROPPED, (data) => { + this.props.onFilesDropped?.(data.tabId, data.files, data.insertPosition); + }); + }; + + public addToUserPrompt = (tabId: string, attachmentContent: string, type?: PromptAttachmentType): void => { + if (Config.getInstance().config.showPromptField && MynahUITabsStore.getInstance().getTab(tabId) !== null) { + this.chatWrappers[tabId].addAttachmentToPrompt(attachmentContent, type); + } + }; + + /** + * Adds a new item to the chat window + * @param tabId Corresponding tab ID. + * @param answer ChatItem object. + */ + public addChatItem = (tabId: string, chatItem: ChatItem): void => { + if (MynahUITabsStore.getInstance().getTab(tabId) !== null) { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CHAT_ITEM_ADD, { tabId, chatItem }); + MynahUITabsStore.getInstance().getTabDataStore(tabId).updateStore({ + chatItems: [ + ...MynahUITabsStore.getInstance().getTabDataStore(tabId).getValue('chatItems'), + chatItem + ] + }); + } + }; + + public addCustomContextToPrompt = (tabId: string, contextItem: QuickActionCommand[], insertPosition?: number): void => { + if (MynahUITabsStore.getInstance().getTab(tabId) !== null) { + // Get the current trigger source from the chat wrapper + const chatWrapper = this.chatWrappers[tabId]; + const currentTriggerSource = chatWrapper?.getCurrentTriggerSource?.() ?? 'prompt-input'; + + if (currentTriggerSource === 'top-bar') { + // If triggered from top bar, add to top bar instead + contextItem.forEach(item => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.CONTEXT_PINNED, { tabId, contextItem: item }); + }); + } else { + // Update the data store's customContextCommand field + const currentCustomContext = MynahUITabsStore.getInstance().getTabDataStore(tabId).getValue('customContextCommand') as QuickActionCommand[] ?? []; + MynahUITabsStore.getInstance().getTabDataStore(tabId).updateStore({ + customContextCommand: [ ...currentCustomContext, ...contextItem ] + }); + + // Dispatch the event for UI updates + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.ADD_CUSTOM_CONTEXT, { tabId, contextCommands: contextItem, insertPosition }); + } + + // Dispatch event to signal context has been inserted + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.RESET_TOP_BAR_CLICKED, { tabId }); + } + }; + + /** + * Updates the last ChatItemType.ANSWER_STREAM chat item + * @param tabId Corresponding tab ID. + * @param updateWith ChatItem object to update with. + */ + public updateLastChatAnswer = (tabId: string, updateWith: Partial): void => { + if (MynahUITabsStore.getInstance().getTab(tabId) != null) { + if (this.chatWrappers[tabId].getLastStreamingMessageId() != null) { + this.chatWrappers[tabId].updateLastChatAnswer(updateWith); + } else { + // We're assuming consumer shouldn't try to update last chat item if it is not a streaming one + // However, to be on the safe side, if there is no streaming card available, we're adding one. + this.addChatItem(tabId, { + type: ChatItemType.ANSWER_STREAM, + body: '', + messageId: generateUID(), + }); + this.chatWrappers[tabId].updateLastChatAnswer(updateWith); + } + } + }; + + /** + * Updates the chat item with the given messageId + * @param tabId Corresponding tab ID. + * @param messageId Corresponding tab ID. + * @param updateWith ChatItem object to update with. + */ + public updateChatAnswerWithMessageId = (tabId: string, messageId: string, updateWith: Partial): void => { + if (MynahUITabsStore.getInstance().getTab(tabId) !== null) { + this.chatWrappers[tabId].updateChatAnswerWithMessageId(messageId, updateWith); + } + }; + + /** + * Serialize all (non-empty) chat messages in a tab into a string + * @param tabId Corresponding tab ID. + * @param format Whether to serialize to markdown or HTML format + */ + public serializeChat = (tabId: string, format: 'markdown' | 'html'): string => { + if (format === 'markdown') { + return serializeMarkdown(tabId); + } + return serializeHtml(tabId); + }; + + /** + * Converts a card to an ANSWER if it is an ANSWER_STREAM + * @param tabId Corresponding tab ID. + * @param messageId Corresponding tab ID. + * @param updateWith Optional, if you like update the card while converting it to + * a normal ANSWER instead of a stream one, you can send a ChatItem object to update with. + */ + public endMessageStream = (tabId: string, messageId: string, updateWith?: Partial): CardRenderDetails => { + if (MynahUITabsStore.getInstance().getTab(tabId) !== null) { + const chatMessage = this.chatWrappers[tabId].getChatItem(messageId); + if (chatMessage != null && ![ ChatItemType.AI_PROMPT, ChatItemType.PROMPT, ChatItemType.SYSTEM_PROMPT ].includes(chatMessage.chatItem.type)) { + this.chatWrappers[tabId].endStreamWithMessageId(messageId, { + type: ChatItemType.ANSWER, + ...updateWith + }); + return chatMessage.renderDetails; + } + } + return { + totalNumberOfCodeBlocks: 0 + }; + }; + + /** + * If exists, switch to a different tab + * @param tabId Tab ID to switch to + * @param eventId last action's user event ID passed from an event binded to mynahUI. + * Without user intent you cannot switch to a different tab + */ + public selectTab = (tabId: string, eventId?: string): void => { + // TODO: until we find a way to confirm the events from the consumer as events, + // eventId === this.lastEventId: This check will be removed + if (MynahUITabsStore.getInstance().getTab(tabId) !== null) { + MynahUITabsStore.getInstance().selectTab(tabId); + } + }; + + /** + * If exists, close the given tab + * @param tabId Tab ID to switch to + * @param eventId last action's user event ID passed from an event binded to mynahUI. + * Without user intent you cannot switch to a different tab + */ + public removeTab = (tabId: string, eventId: string): void => { + if (eventId === this.lastEventId && MynahUITabsStore.getInstance().getTab(tabId) !== null) { + MynahUITabsStore.getInstance().removeTab(tabId); + } + }; + + /** + * Updates only the UI with the given data for the given tab + * Send tab id as an empty string to open a new tab! + * If max tabs reached, will not return tabId + * @param data A full or partial set of data with values. + */ + public updateStore = (tabId: string | '', data: MynahUIDataModel): string | undefined => { + if (tabId === '') { + return MynahUITabsStore.getInstance().addTab({ store: { ...data } }); + } else if (MynahUITabsStore.getInstance().getTab(tabId) !== null) { + MynahUITabsStore.getInstance().updateTab(tabId, { store: { ...data } }); + } + return tabId; + }; + + /** + * Updates defaults of the tab store + * @param defaults MynahUITabStoreTab + */ + public updateTabDefaults = (defaults: MynahUITabStoreTab): void => { + MynahUITabsStore.getInstance().updateTabDefaults(defaults); + }; + + /** + * Updates defaults of the tab store + * @param defaults MynahUITabStoreTab + */ + public getTabDefaults = (): MynahUITabStoreTab => { + return MynahUITabsStore.getInstance().getTabDefaults(); + }; + + /** + * This function returns the selected tab id if there is any, otherwise returns undefined + * @returns string selectedTabId or undefined + */ + public getSelectedTabId = (): string | undefined => { + const selectedTabId = MynahUITabsStore.getInstance().getSelectedTabId(); + return selectedTabId === '' ? undefined : selectedTabId; + }; + + /** + * Returns all tabs with their store information + * @returns string selectedTabId or undefined + */ + public getAllTabs = (): MynahUITabStoreModel => MynahUITabsStore.getInstance().getAllTabs(); + + public getTabData = (tabId: string): MynahUIDataStore => MynahUITabsStore.getInstance().getTabDataStore(tabId); + + /** + * Gets the current prompt input text for a tab (the text the user is typing) + * @param tabId The tab ID to get the prompt input text for + * @returns The current prompt input text, or empty string if tab not found + */ + public getPromptInputText = (tabId: string): string => { + const chatWrapper = this.chatWrappers[tabId]; + if (chatWrapper != null) { + return chatWrapper.getPromptInputText(); + } + return ''; + }; + + /** + * Sets the drag overlay visibility for a specific tab + * @param tabId The tab ID to set the drag overlay visibility for + * @param visible Whether the drag overlay should be visible + */ + public setDragOverlayVisible = (tabId: string, visible: boolean): void => { + if (this.chatWrappers[tabId] !== null) { + this.chatWrappers[tabId].setDragOverlayVisible(visible); + } + }; + + /** + * Programmatically resets topBarClicked for the specified tab by dispatching a RESET_TOP_BAR_CLICKED event. + * @param tabId The tab ID + */ + public resetTopBarClicked = (tabId: string): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.RESET_TOP_BAR_CLICKED, { tabId }); + }; + + /** + * Toggles the visibility of the splash loader screen + */ + public toggleSplashLoader = (visible: boolean, text?: string, actions?: Action[]): void => { + if (visible) { + this.splashLoader.addClass('visible'); + } else { + this.splashLoader.removeClass('visible'); + } + + if (text != null) { + this.splashLoaderText.update({ + innerHTML: parseMarkdown(text, { includeLineBreaks: true }) + }); + } + + this.splashLoaderActions.clear(); + this.splashLoaderActions.update({ + children: this.getSplashLoaderActions(actions) + }); + }; + + /** + * Simply creates and shows a notification + * @param props NotificationProps + */ + public notify = (props: { + /** + * -1 for infinite + */ + duration?: number; + type?: NotificationType; + title?: string; + content: string; + onNotificationClick?: (eventId: string) => void; + onNotificationHide?: (eventId: string) => void; + }): void => { + new Notification({ + ...props, + onNotificationClick: (props.onNotificationClick != null) + ? () => { + if (props.onNotificationClick != null) { + props.onNotificationClick(this.getUserEventId()); + } + } + : undefined, + onNotificationHide: (props.onNotificationHide != null) + ? () => { + if (props.onNotificationHide != null) { + props.onNotificationHide(this.getUserEventId()); + } + } + : undefined, + }).notify(); + }; + + /** + * Simply creates and shows a custom form + */ + public showCustomForm = ( + tabId: string, + formItems?: ChatItemFormItem[], + buttons?: ChatItemButton[], + title?: string, + description?: string): void => { + MynahUIGlobalEvents.getInstance().dispatch(MynahEventNames.SHOW_FEEDBACK_FORM, { + tabId, + customFormData: { + title, + description, + buttons, + formItems + }, + }); + }; + + public openTopBarButtonOverlay = (data: TopBarButtonOverlayProps, + ): { + update: (data: DetailedList) => void; + close: () => void; + + } => { + if (data.tabId != null) { + this.chatWrappers[data.tabId].openTopBarButtonItemOverlay(data); + } + return { + update: this.chatWrappers[data.tabId].updateTopBarButtonItemOverlay, + close: this.chatWrappers[data.tabId].closeTopBarButtonItemOverlay + }; + }; + + public openDetailedList = ( + data: DetailedListSheetProps, + showBackButton?: boolean, + ): { + update: (data: DetailedList, showBackButton?: boolean) => void; + close: () => void; + changeTarget: (direction: 'up' | 'down', snapOnLastAndFirst?: boolean) => void; + getTargetElementId: () => string | undefined; + } => { + const detailedListSheet = new DetailedListSheet({ + detailedList: data.detailedList, + events: data.events + }); + detailedListSheet.open(showBackButton); + + const getTargetElementId = (): string | undefined => { + const targetElement = detailedListSheet.detailedListWrapper.getTargetElement(); + return targetElement?.id ?? undefined; + }; + return { + update: detailedListSheet.update, + close: detailedListSheet.close, + changeTarget: detailedListSheet.detailedListWrapper.changeTarget, + getTargetElementId + }; + }; + + public destroy = (): void => { + // Destroy all chat wrappers + Object.values(this.chatWrappers).forEach(chatWrapper => { + chatWrapper.destroy(); + }); + + Config.getInstance().destroy(); + MynahUITabsStore.getInstance().destroy(); + MynahUIGlobalEvents.getInstance().destroy(); + DomBuilder.getInstance().destroy(); + }; +} diff --git a/vendor/mynah-ui/src/modules.d.ts b/vendor/mynah-ui/src/modules.d.ts new file mode 100644 index 00000000..742b5325 --- /dev/null +++ b/vendor/mynah-ui/src/modules.d.ts @@ -0,0 +1,4 @@ +declare module 'escape-html' { + function escapeHTML (a: string): string; + export = escapeHTML; +} diff --git a/vendor/mynah-ui/src/static.ts b/vendor/mynah-ui/src/static.ts new file mode 100644 index 00000000..7a61c4a3 --- /dev/null +++ b/vendor/mynah-ui/src/static.ts @@ -0,0 +1,857 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +import { CheckboxAbstract, CheckboxProps } from './components/form-items/checkbox'; +import { FormItemListAbstract, FormItemListProps } from './components/form-items/form-item-list'; +import { FormItemPillBoxAbstract, FormItemPillBoxProps } from './components/form-items/form-item-pill-box'; +import { SwitchAbstract, SwitchProps } from './components/form-items/switch'; +import { CustomIcon, MynahIcons, MynahIconsType } from './components/icon'; +import { ChatItemBodyRenderer } from './helper/dom'; +import { + SelectAbstract, + SelectProps, + RadioGroupAbstract, + RadioGroupProps, + ButtonAbstract, + ButtonProps, + TextInputProps, + TextInputAbstract, + TextAreaProps, + TextAreaAbstract, + ToggleOption, +} from './main'; + +export interface QuickActionCommand { + command: string; + id?: string; + label?: string; + disabled?: boolean; + icon?: MynahIcons | MynahIconsType; + description?: string; + placeholder?: string; + children?: QuickActionCommandGroup[]; + route?: string[]; + disabledText?: string; +} +export interface CustomQuickActionCommand extends QuickActionCommand { + content?: Uint8Array; +} + +export interface QuickActionCommandGroup { + groupName?: string; + icon?: MynahIcons | MynahIconsType; + actions?: Action[]; + commands: QuickActionCommand[]; +} + +export interface QuickActionCommandsHeader { + icon?: MynahIcons | MynahIconsType; + title?: string; + description?: string; + status?: Status; +} +/** + * data store model to update the mynah ui partially or fully + */ +export interface MynahUIDataModel { + /** + * Tab title + * */ + tabTitle?: string; + /** + * Tab icon + * */ + tabIcon?: MynahIcons | MynahIconsType | null; + /** + * is tab pinned + * */ + pinned?: boolean; + /** + * Tab title + * */ + tabBackground?: boolean; + /** + * If tab is running an action (loadingChat = true) this markdown will be shown before close in a popup + */ + tabCloseConfirmationMessage?: string | null; + /** + * Keep tab open button text + */ + tabCloseConfirmationKeepButton?: string | null; + /** + * Close tab button text + */ + tabCloseConfirmationCloseButton?: string | null; + /** + * Chat screen loading animation state (mainly use during the stream or getting the initial answer) + */ + loadingChat?: boolean; + /** + * Show chat avatars or not + * */ + showChatAvatars?: boolean; + /** + * Show cancel button while loading the chat + * */ + cancelButtonWhenLoading?: boolean; + /** + * Quick Action commands to show when user hits / to the input initially + */ + quickActionCommands?: QuickActionCommandGroup[]; + /** + * Quick Action commands header information block + */ + quickActionCommandsHeader?: QuickActionCommandsHeader; + /** + * Context commands to show when user hits @ to the input any point + */ + contextCommands?: QuickActionCommandGroup[]; + /** + * Placeholder to be shown on prompt input + */ + promptInputPlaceholder?: string; + + /** + * Title to be shown on prompt top bar + */ + promptTopBarTitle?: string; + + /** + * Items to be pinned in prompt top bar + */ + promptTopBarContextItems?: QuickActionCommand[]; + + /** + * Button to display at end of prompt top bar + */ + promptTopBarButton?: ChatItemButton | null; + /** + * Prompt input text + */ + promptInputText?: string; + /** + * Label to be shown on top of the prompt input + */ + promptInputLabel?: string | null; + /** + * Label to be shown on top of the prompt input + */ + promptInputVisible?: boolean; + /** + * Info block to be shown under prompt input + */ + promptInputInfo?: string; + /** + * A sticky chat item card on top of the prompt input + */ + promptInputStickyCard?: Partial | null; + /** + * Prompt input field disabled state, set to tru to disable it + */ + promptInputDisabledState?: boolean; + /** + * Prompt input progress field + */ + promptInputProgress?: ProgressField | null; + /** + * Prompt input options/form items + */ + promptInputOptions?: FilterOption[] | null; + /** + * Prompt input button items + */ + promptInputButtons?: ChatItemButton[] | null; + /** + * List of chat item objects to be shown on the web suggestions search screen + */ + chatItems?: ChatItem[]; + /** + * Attached code under the prompt input field + */ + selectedCodeSnippet?: string; + /** + * Tab bar buttons next to the tab items + */ + tabBarButtons?: TabBarMainAction[]; + /** + * Tab content compact mode which keeps everything in the middle + */ + compactMode?: boolean; + /** + * Tab content header details, only visibile when showTabHeaderDetails is set to 'true' + */ + tabHeaderDetails?: TabHeaderDetails | null; + /** + * A lightweight key-value store for essential tab-specific primitive metadata. + * Not intended for storing large amounts of data - use appropriate + * application state management for that purpose. + */ + tabMetadata?: { [key: string]: string | boolean | number }; + /** + * Custom context commands to be inserted into the prompt input. + */ + customContextCommand?: QuickActionCommand[]; +} + +export interface MynahUITabStoreTab { + /** + * Is tab selected + */ + isSelected?: boolean; + /** + * Tab items data store + */ + store?: MynahUIDataModel; +} +/** + * tabs store model to update the tabs partially or fully + */ +export interface MynahUITabStoreModel { + [tabId: string]: MynahUITabStoreTab; +} + +export enum MynahEventNames { + RESET_STORE = 'resetStore', + FEEDBACK_SET = 'feedbackSet', + ROOT_FOCUS = 'rootFocusStateChange', + CARD_VOTE = 'cardVote', + SOURCE_LINK_CLICK = 'sourceLinkClick', + INFO_LINK_CLICK = 'infoLinkClick', + FORM_LINK_CLICK = 'formLinkClick', + LINK_CLICK = 'linkClick', + CHAT_ITEM_ENGAGEMENT = 'chatItemEngagement', + COPY_CODE_TO_CLIPBOARD = 'copyCodeToClipboard', + CODE_BLOCK_ACTION = 'codeBlockAction', + CHAT_PROMPT = 'chatPrompt', + CHAT_ITEM_ADD = 'chatItemAdd', + FOLLOW_UP_CLICKED = 'followUpClicked', + BODY_ACTION_CLICKED = 'bodyActionClicked', + QUICK_COMMAND_GROUP_ACTION_CLICK = 'quickCommandGroupActionClicked', + TABBED_CONTENT_SWITCH = 'tabbedContentSwitch', + SHOW_MORE_WEB_RESULTS_CLICK = 'showMoreWebResultsClick', + SHOW_FEEDBACK_FORM = 'showFeedbackForm', + OPEN_SHEET = 'openSheet', + CLOSE_SHEET = 'closeSheet', + UPDATE_SHEET = 'updateSheet', + FILE_CLICK = 'fileClick', + FILE_ACTION_CLICK = 'fileActionClick', + TAB_FOCUS = 'tabFocus', + CUSTOM_FORM_ACTION_CLICK = 'customFormActionClick', + DROPDOWN_OPTION_CHANGE = 'dropdownOptionChange', + DROPDOWN_LINK_CLICK = 'dropdownLinkClick', + PROMPT_INPUT_OPTIONS_CHANGE = 'promptInputOptionsChange', + PROMPT_INPUT_BUTTON_CLICK = 'promptInputButtonClick', + FORM_MODIFIER_ENTER_PRESS = 'formModifierEnterPress', + FORM_TEXTUAL_ITEM_KEYPRESS = 'formTextualItemKeyPress', + FORM_CHANGE = 'formChange', + ADD_ATTACHMENT = 'addAttachment', + CARD_DISMISS = 'cardDismiss', + REMOVE_ATTACHMENT = 'removeAttachment', + TAB_BAR_BUTTON_CLICK = 'tabBarButtonClick', + PROMPT_PROGRESS_ACTION_CLICK = 'promptProgressActionClick', + ROOT_RESIZE = 'rootResize', + CONTEXT_SELECTED = 'contextSelected', + OPEN_FILE_SYSTEM = 'openFileSystem', + ADD_CUSTOM_CONTEXT = 'addCustomContext', + TOP_BAR_ITEM_ADD = 'promptInputTopBarItemAdd', + TOP_BAR_ITEM_REMOVE = 'promptInputTopBarItemRemove', + TOP_BAR_BUTTON_CLICK = 'promptInputTopBarButtonClick', + CONTEXT_PINNED = 'contextPinned', + FILES_DROPPED = 'filesDropped', + RESET_TOP_BAR_CLICKED = 'resetTopBarClicked' +} + +export enum MynahPortalNames { + WRAPPER = 'wrapper', + SIDE_NAV = 'sideNav', + OVERLAY = 'overlay', + SHEET = 'sheet', + LOADER = 'loader', +}; + +export type PromptAttachmentType = 'code' | 'markdown'; + +export interface SourceLinkMetaData { + stars?: number; // repo stars + forks?: number; // repo forks + answerCount?: number; // total answers if it is a question + isOfficialDoc?: boolean; // is suggestion comes from an official api doc + isAccepted?: boolean; // is accepted or not if it is an answer + score?: number; // relative score according to the up and down votes for a question or an answer + lastActivityDate?: number; // creation or last update date for question or answer +} + +export interface SourceLink { + title: string; + id?: string; + url: string; + body?: string; + type?: string; + metadata?: Record; +} +export enum ChatItemType { + PROMPT = 'prompt', + DIRECTIVE = 'directive', + SYSTEM_PROMPT = 'system-prompt', + AI_PROMPT = 'ai-prompt', + ANSWER = 'answer', + ANSWER_STREAM = 'answer-stream', + ANSWER_PART = 'answer-part', + CODE_RESULT = 'code-result', +} + +export interface DetailedList { + filterOptions?: FilterOption[] | null; + filterActions?: ChatItemButton[]; + list?: DetailedListItemGroup[]; + header?: { + title?: string; + icon?: MynahIcons | MynahIconsType; + status?: { + icon?: MynahIcons | MynahIconsType; + title?: string; + description?: string; + status?: Status; + }; + description?: string; + actions?: TabBarMainAction[]; + }; + selectable?: boolean | 'clickable'; + textDirection?: 'row' | 'column'; +} + +export interface DetailedListItemGroup { + groupName?: string; + actions?: Action[]; + icon?: MynahIcons | MynahIconsType; + children?: DetailedListItem[]; + childrenIndented?: boolean; +} + +export interface DetailedListItem { + title?: string; + name?: string; + id?: string; + icon?: MynahIcons | MynahIconsType; + iconForegroundStatus?: Status; + description?: string; + disabled?: boolean; + followupText?: string; + actions?: ChatItemButton[]; + groupActions?: boolean; + children?: DetailedListItemGroup[]; + keywords?: string[]; + status?: { + status?: Status; + description?: string; + icon?: MynahIcons | MynahIconsType; + text?: string; + }; + disabledText?: string; +} + +export type Status = 'info' | 'success' | 'warning' | 'error'; + +export interface ProgressField { + /** + * Prompt input progress status + */ + status?: 'default' | Status; + /** + * Prompt input progress text + */ + text?: string; + /** + * Prompt input progress text for the current state (ie: 15%, 2min remaining) + */ + valueText?: string; + /** + * Prompt input progress value to show the inner bar state, -1 for infinite + */ + value?: number; + /** + * Prompt input progress actions + */ + actions?: ChatItemButton[]; +} + +export interface TreeNodeDetails { + status?: Status; + visibleName?: string; + icon?: MynahIcons | MynahIconsType | null; + iconForegroundStatus?: Status; + labelIcon?: MynahIcons | MynahIconsType | null; + labelIconForegroundStatus?: Status; + label?: string; + changes?: { + added?: number; + deleted?: number; + total?: number; + }; + description?: string; + clickable?: boolean; + data?: Record; +} + +export interface DropdownListOption { + id: string; + label: string; + value: string; + selected?: boolean; +} + +export interface DropdownListProps { + description?: string; + descriptionLink?: { + id: string; + text: string; + destination: string; + onClick?: () => void; + }; + options: DropdownListOption[]; + onChange?: (selectedOptions: DropdownListOption[]) => void; + tabId?: string; + messageId?: string; + classNames?: string[]; +} + +export interface DropdownFactoryProps extends DropdownListProps { + type: 'select' | 'radio' | 'checkbox'; +} + +export interface ChatItemContent { + header?: (ChatItemContent & { + icon?: MynahIcons | MynahIconsType | CustomIcon; + iconStatus?: 'main' | 'primary' | 'clear' | Status; + iconForegroundStatus?: Status; + status?: { + status?: Status; + position?: 'left' | 'right'; + description?: string; + icon?: MynahIcons | MynahIconsType; + text?: string; + }; + }) | null; + body?: string | null; + customRenderer?: string | ChatItemBodyRenderer | ChatItemBodyRenderer[] | null; + followUp?: { + text?: string; + options?: ChatItemAction[]; + } | null; + relatedContent?: { + title?: string; + content: SourceLink[]; + } | null; + codeReference?: ReferenceTrackerInformation[] | null; + fileList?: { + fileTreeTitle?: string; + rootFolderTitle?: string; + rootFolderStatusIcon?: MynahIcons | MynahIconsType; + rootFolderStatusIconForegroundStatus?: Status; + rootFolderLabel?: string; + filePaths?: string[]; + deletedFiles?: string[]; + flatList?: boolean; + folderIcon?: MynahIcons | MynahIconsType | null; + collapsed?: boolean; + hideFileCount?: boolean; + actions?: Record; + details?: Record; + renderAsPills?: boolean; + } | null; + buttons?: ChatItemButton[] | null; + formItems?: ChatItemFormItem[] | null; + footer?: ChatItemContent | null; + informationCard?: { + title?: string; + status?: { + status?: Status; + icon?: MynahIcons | MynahIconsType; + body?: string; + }; + description?: string; + icon?: MynahIcons | MynahIconsType; + content: ChatItemContent; + } | null; + summary?: { + isCollapsed?: boolean; + content?: ChatItemContent; + collapsedContent?: ChatItemContent[]; + } | null; + tabbedContent?: Array | null; + quickSettings?: DropdownFactoryProps | null; + codeBlockActions?: CodeBlockActions | null; + fullWidth?: boolean; + padding?: boolean; + wrapCodes?: boolean; + muted?: boolean; +} + +export interface ChatItem extends ChatItemContent { + type: ChatItemType; + messageId?: string; + snapToTop?: boolean; + autoCollapse?: boolean; + contentHorizontalAlignment?: 'default' | 'center'; + canBeVoted?: boolean; + canBeDismissed?: boolean; + title?: string; + icon?: MynahIcons | MynahIconsType | CustomIcon; + iconForegroundStatus?: Status; + iconStatus?: 'main' | 'primary' | 'clear' | Status; + hoverEffect?: boolean; + status?: Status; + shimmer?: boolean; + collapse?: boolean; + border?: boolean; +} + +export interface ValidationPattern { + pattern: string | RegExp; + errorMessage?: string; +} + +interface BaseFormItem { + id: string; + mandatory?: boolean; + hideMandatoryIcon?: boolean; + title?: string; + placeholder?: string; + value?: string; + description?: string; + tooltip?: string; + icon?: MynahIcons | MynahIconsType; + boldTitle?: boolean; +} + +export type TextBasedFormItem = BaseFormItem & { + type: 'textarea' | 'textinput' | 'numericinput' | 'email' | 'pillbox'; + autoFocus?: boolean; + checkModifierEnterKeyPress?: boolean; + validationPatterns?: { + operator?: 'and' | 'or'; + genericValidationErrorMessage?: string; + patterns: ValidationPattern[]; + }; + validateOnChange?: boolean; + disabled?: boolean; +}; + +type DropdownFormItem = BaseFormItem & { + type: 'select'; + border?: boolean; + autoWidth?: boolean; + options?: Array<{ + value: string; + label: string; + description?: string; + }>; + disabled?: boolean; + selectTooltip?: string; +}; + +type Stars = BaseFormItem & { + type: 'stars'; + options?: Array<{ + value: string; + label: string; + }>; +}; + +type RadioGroupFormItem = BaseFormItem & { + type: 'radiogroup' | 'toggle'; + options?: Array<{ + value: string; + label?: string; + icon?: MynahIcons | MynahIconsType; + }>; + disabled?: boolean; +}; + +type CheckboxFormItem = BaseFormItem & { + type: 'switch' | 'checkbox'; + value?: 'true' | 'false'; + label?: string; + alternateTooltip?: string; +}; + +export type SingularFormItem = TextBasedFormItem | DropdownFormItem | RadioGroupFormItem | CheckboxFormItem | Stars; +export type ChatItemFormItem = TextBasedFormItem | DropdownFormItem | RadioGroupFormItem | CheckboxFormItem | ListFormItem | Stars; +export type FilterOption = ChatItemFormItem; + +export interface ListFormItem { + type: 'list'; + id: string; + mandatory?: boolean; + hideMandatoryIcon?: boolean; + title?: string; + description?: string; + tooltip?: string; + icon?: MynahIcons | MynahIconsType; + boldTitle?: boolean; + items: SingularFormItem[]; + value: ListItemEntry[]; + disabled?: boolean; +}; + +export interface ListItemEntry { + persistent?: boolean; + value: Record; +} + +export interface ChatPrompt { + prompt?: string; + escapedPrompt?: string; + command?: string; + options?: Record>>; + context?: string[] | QuickActionCommand[]; +} + +export interface ChatItemAction extends ChatPrompt { + type?: string; + pillText: string; + disabled?: boolean; + description?: string; + status?: 'primary' | Status; + icon?: MynahIcons | MynahIconsType; +} +export interface ChatItemButton extends Omit { + keepCardAfterClick?: boolean; + waitMandatoryFormItems?: boolean; + status?: 'main' | 'primary' | 'clear' | 'dimmed-clear' | Status; + flash?: 'infinite' | 'once'; + fillState?: 'hover' | 'always'; + position?: 'inside' | 'outside'; +} +export interface Action { + text?: string; + id: string; + disabled?: boolean; + description?: string; + confirmation?: { + confirmButtonText: string; + cancelButtonText: string; + title: string; + description?: string; + }; + status?: 'main' | 'primary' | 'clear' | 'dimmed-clear' | Status; + icon?: MynahIcons | MynahIconsType; +} +export interface TabBarAction extends Action {} + +export interface TabBarMainAction extends TabBarAction { + items?: TabBarAction[]; +} + +export interface FileNodeAction { + name: string; + label?: string; + disabled?: boolean; + description?: string; + status?: Status; + icon: MynahIcons | MynahIconsType; +} + +export enum KeyMap { + ESCAPE = 'Escape', + ENTER = 'Enter', + BACKSPACE = 'Backspace', + SPACE = ' ', + DELETE = 'Delete', + ARROW_UP = 'ArrowUp', + ARROW_DOWN = 'ArrowDown', + ARROW_LEFT = 'ArrowLeft', + ARROW_RIGHT = 'ArrowRight', + PAGE_UP = 'PageUp', + PAGED_OWN = 'PageDown', + HOME = 'Home', + END = 'End', + META = 'Meta', + TAB = 'Tab', + SHIFT = 'Shift', + CONTROL = 'Control', + ALT = 'Alt', + AT = '@', + SLASH = '/', + BACK_SLASH = '\\' +} + +export interface ReferenceTrackerInformation { + licenseName?: string; + repository?: string; + url?: string; + recommendationContentSpan?: { + start: number; + end: number; + }; + information: string; +} + +export type CodeSelectionType = 'selection' | 'block'; +export type OnCopiedToClipboardFunction = (type?: CodeSelectionType, text?: string, referenceTrackerInformation?: ReferenceTrackerInformation[], codeBlockIndex?: number, totalCodeBlocks?: number) => void; +export type OnCodeBlockActionFunction = (actionId: string, data?: any, type?: CodeSelectionType, text?: string, referenceTrackerInformation?: ReferenceTrackerInformation[], codeBlockIndex?: number, totalCodeBlocks?: number) => void; + +export enum RelevancyVoteType { + UP = 'upvote', + DOWN = 'downvote', +} + +/** + * 'interaction' will be set if there was a potential text selection or a click input was triggered by the user. + * If this is a selection selectionDistanceTraveled object will also be filled + * 'timespend' will be set basically if there is no interaction except mouse movements in a time spent longer than the ENGAGEMENT_DURATION_LIMIT + * Don't forget that in 'timespend' case, user should leave the suggestion card at some point to count it as an interaction. + * (They need to go back to the code or move to another card instead) + */ +export enum EngagementType { + INTERACTION = 'interaction', + TIME = 'timespend', +} + +export interface Engagement { + /** + * Engagement type + */ + engagementType: EngagementType; + /** + * Total duration in ms till the engagement triggered. + */ + engagementDurationTillTrigger: number; + /** + * Total mouse movement in x and y directions till the engagement triggered. + * To avoid confusion: this is not the distance between start and end points, this is the total traveled distance. + */ + totalMouseDistanceTraveled: { x: number; y: number }; + /** + * If the engagementType is "interaction" and this object has a value, you can assume it as a text selection. + * If the engagementType is "interaction" but this object is not defined, you can assume it as a click + */ + selectionDistanceTraveled?: { x: number; y: number; selectedText?: string }; +} + +export interface FeedbackPayload { + messageId: string; + tabId: string; + selectedOption: string; + comment?: string; +} + +export enum NotificationType { + INFO = MynahIcons.INFO, + SUCCESS = MynahIcons.OK_CIRCLED, + WARNING = MynahIcons.WARNING, + ERROR = MynahIcons.ERROR, +} + +export interface TabHeaderDetails { + icon?: MynahIcons | MynahIconsType; + title?: string; + description?: string; +} + +export interface CodeBlockAction { + id: 'copy' | 'insert-to-cursor' | string; + label: string; + description?: string; + icon?: MynahIcons | MynahIconsType; + data?: any; + flash?: 'infinite' | 'once'; + acceptedLanguages?: string[]; +} +export type CodeBlockActions = Record<'copy' | 'insert-to-cursor' | string, CodeBlockAction | undefined | null>; + +export interface ConfigTexts { + mainTitle: string; + feedbackFormTitle: string; + feedbackFormDescription: string; + feedbackFormOptionsLabel: string; + feedbackFormCommentLabel: string; + feedbackThanks: string; + feedbackReportButtonLabel: string; + codeSuggestions: string; + clickFileToViewDiff: string; + files: string; + changes: string; + insertAtCursorLabel: string; + copy: string; + showMore: string; + save: string; + cancel: string; + submit: string; + add: string; + pleaseSelect: string; + stopGenerating: string; + stopGeneratingTooltip?: string; + copyToClipboard: string; + noMoreTabsTooltip: string; + codeSuggestionWithReferenceTitle: string; + spinnerText: string; + tabCloseConfirmationMessage: string; + tabCloseConfirmationKeepButton: string; + tabCloseConfirmationCloseButton: string; + noTabsOpen: string; + openNewTab: string; + commandConfirmation: string; + pinContextHint: string; + dragOverlayText: string; +} + +type PickMatching = { + [K in keyof T as T[K] extends V ? K : never]: T[K]; +}; +type ExtractMethods = PickMatching; + +export interface ComponentOverrides { + Button?: new(props: ButtonProps) => ExtractMethods; + RadioGroup?: new(props: RadioGroupProps) => ExtractMethods; + Checkbox?: new(props: CheckboxProps) => ExtractMethods; + Switch?: new(props: SwitchProps) => ExtractMethods; + Select?: new(props: SelectProps) => ExtractMethods; + TextInput?: new(props: TextInputProps) => ExtractMethods; + TextArea?: new(props: TextAreaProps) => ExtractMethods; + FormItemList?: new(props: FormItemListProps) => ExtractMethods; + FormItemPillBox?: new(props: FormItemPillBoxProps) => ExtractMethods; +}; +export interface ConfigOptions { + feedbackOptions: Array<{ + label: string; + value: string; + }>; + tabBarButtons?: TabBarMainAction[]; + maxTabs: number; + maxTabsTooltipDuration?: number; + noMoreTabsTooltip?: string; + showPromptField: boolean; + autoFocus: boolean; + maxUserInput: number; + userInputLengthWarningThreshold: number; + codeBlockActions?: CodeBlockActions; + codeInsertToCursorEnabled?: boolean; + codeCopyToClipboardEnabled?: boolean; + test?: boolean; + dragOverlayIcon?: MynahIcons | MynahIconsType | CustomIcon; + enableSearchKeyboardShortcut?: boolean; + typewriterStackTime?: number; + typewriterMaxWordTime?: number; + disableTypewriterAnimation?: boolean; + /** + * When true, sending the prompt requires a modifier key: + * - Shift+Enter or Cmd/Ctrl+Enter sends the prompt + * - Enter alone inserts a newline + * When false (default), Enter sends and Shift+Enter inserts newline. + */ + requireModifierToSendPrompt?: boolean; +} + +export interface ConfigModel extends ConfigOptions { + texts: Partial; + componentOverrides: Partial; +} + +export interface CardRenderDetails { + totalNumberOfCodeBlocks?: number; +} diff --git a/vendor/mynah-ui/src/styles/_animations.scss b/vendor/mynah-ui/src/styles/_animations.scss new file mode 100644 index 00000000..c86a2a6b --- /dev/null +++ b/vendor/mynah-ui/src/styles/_animations.scss @@ -0,0 +1,105 @@ +@keyframes horizontal-roll { + 0% { + background-position: 0% top; + } + 100% { + background-position: -200% top; + } +} + +@keyframes bounce { + from { + transform: translate3d(0, 0, 0); + } + to { + transform: translate3d(0, calc(-1 * var(--mynah-sizing-2)), 0); + } +} + +@keyframes spinner-spin { + to { + transform: rotate(360deg); + } +} + +@keyframes spinner-spin-delayed { + 90% { + transform: rotate(360deg); + } + 100% { + transform: rotate(360deg); + } +} +@keyframes spinner-spin-delayed-reverse { + 90% { + transform: rotate(-360deg); + } + 100% { + transform: rotate(-360deg); + } +} + +@keyframes flash-main { + 0% { + filter: brightness(0.925); + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); + transform: scale(1); + } + 10% { + box-shadow: 0px 0px 12px -10px currentColor; + } + 15% { + filter: brightness(1.25); + box-shadow: 0px 0px 12px -10px currentColor; + } + 35% { + filter: brightness(0.925); + box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); + transform: scale(1); + } + 100% { + filter: brightness(0.925); + } +} +@keyframes flash-pseudo { + 0% { + transform: translate3d(-100%, 0, 0); + opacity: 0; + } + 10% { + transform: translate3d(0%, 0, 0); + opacity: 0.25; + } + 15% { + transform: translate3d(0%, 0, 0); + opacity: 0.25; + } + 35% { + transform: translate3d(110%, 0, 0); + opacity: 0; + } + 100% { + transform: translate3d(110%, 0, 0); + opacity: 0; + } +} +@keyframes opacity { + 45% { + opacity: 0.85; + } + 55% { + opacity: 0.85; + } + 90% { + opacity: 0.25; + } +} + +@keyframes typewriter-reveal { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} diff --git a/vendor/mynah-ui/src/styles/_dark.scss b/vendor/mynah-ui/src/styles/_dark.scss new file mode 100644 index 00000000..e69de29b diff --git a/vendor/mynah-ui/src/styles/_mixins.scss b/vendor/mynah-ui/src/styles/_mixins.scss new file mode 100644 index 00000000..e3bbd07b --- /dev/null +++ b/vendor/mynah-ui/src/styles/_mixins.scss @@ -0,0 +1,156 @@ +@import './scss-variables'; +@mixin list-fader-bottom($height: var(--mynah-sizing-6)) { + position: relative; + $listFader: linear-gradient(to bottom, black 30%, black calc(100% - $height), transparent 100%); + -webkit-mask-image: $listFader; + -webkit-mask-size: 100% 100%; + mask-image: $listFader; + mask-size: 100% 100%; +} + +@mixin gradient-border($gradient: var(--mynah-color-gradient-main), $background: var(--mynah-card-bg)) { + background-clip: padding-box, border-box; + background-origin: border-box; + background-image: linear-gradient($background, $background), $gradient; + background-position: 0% bottom; + background-size: 200% 100%; + background-repeat: repeat-x; + border: var(--mynah-border-width) solid transparent; +} + +/** + * @param {string} $size - The size of the font. + * @param {string} $weight - The weight of the font. + * @param {string} $family - The family of the font. + */ +@mixin mynah-font($size, $weight: normal, $family: var(--mynah-font-family)) { + font-size: $size; + font-weight: $weight; + font-family: $family; +} + +/** + * @param {string} $text-color - The color of the text. + * @param {string} $bg-color - The background color. + */ +@mixin mynah-color($text-color, $bg-color) { + color: $text-color; + background-color: $bg-color; +} + +/** + * @param {string} $margin - The margin of the element. + * @param {string} $padding - The padding of the element. + */ +@mixin mynah-spacing($margin: 0, $padding: 0) { + margin: $margin; + padding: $padding; +} + +/** + * @param {string} $radius - The radius of the border. + */ +@mixin mynah-border-radius($radius) { + border-radius: $radius; +} + +/** + * @param {string} $shadow - The shadow of the element. + */ +@mixin mynah-box-shadow($shadow) { + box-shadow: $shadow; +} + +/** + * @param {string} $name - The name of the transition. + * @param {number} $duration - The duration of the transition in milliseconds. + * @param {string} $timing-function - The timing function of the transition. + */ +@mixin mynah-transition($name, $duration, $timing-function) { + --mynah-#{$name}-transition: all #{$duration}ms #{$timing-function}; +} + +@mixin full-width-header() { + min-width: 100%; + box-sizing: border-box; + gap: var(--mynah-sizing-1); + > .mynah-ui-chat-item-small-card { + flex: 1; + > .mynah-card.padding-none { + display: grid; + grid-template-columns: auto 1fr auto; + grid-template-rows: auto; + padding: 0; + column-gap: 0; + align-items: center; + > .mynah-ui-icon { + margin-right: var(--mynah-sizing-2); + &[class*='icon-status'] + .mynah-card-body { + align-self: center; + } + } + > .mynah-chat-item-buttons-container { + margin-left: var(--mynah-sizing-2); + } + > * { + grid-column: 2; + } + > .mynah-card-body { + &, + & *:not(h1):not(h2):not(h3):not(h4:not(h5):not(h6)) { + font-size: var(--mynah-font-size-medium); + } + & h1, + & h2, + & h3, + & h4, + & h5, + & h6 { + font-size: revert; + } + } + > .mynah-ui-icon { + grid-column: 1; + align-self: center; + position: relative; + transform: translate(0px, 0px); + font-size: var(--mynah-font-size-large); + } + > .mynah-chat-item-buttons-container { + grid-column: 3; + gap: var(--mynah-border-width); + } + > .mynah-chat-item-tree-view-wrapper { + padding: 0; + min-width: auto; + width: 100%; + > .mynah-chat-item-tree-view-wrapper-container { + border: none; + } + } + } + } +} + +@mixin text-shimmer { + @keyframes shimmer { + 0% { + background-position: -100% 0; + } + 100% { + background-position: 100% 0; + } + } + + background: linear-gradient( + 90deg, + var(--mynah-color-text-default) 0%, + rgba(135, 135, 135, 0.4) 50%, + var(--mynah-color-text-default) 100% + ); + background-size: 50% 100%; + background-clip: text; + -webkit-background-clip: text; + color: transparent; + animation: shimmer 3s infinite linear; +} diff --git a/vendor/mynah-ui/src/styles/_scrollbars.scss b/vendor/mynah-ui/src/styles/_scrollbars.scss new file mode 100644 index 00000000..4cb647b7 --- /dev/null +++ b/vendor/mynah-ui/src/styles/_scrollbars.scss @@ -0,0 +1,4 @@ +* { + scrollbar-gutter: unset; + scrollbar-color: color-mix(in hsl, var(--mynah-card-bg-alternate), hsla(0, 0%, 100%, 0.2) 90%) transparent; +} diff --git a/vendor/mynah-ui/src/styles/_scss-variables.scss b/vendor/mynah-ui/src/styles/_scss-variables.scss new file mode 100644 index 00000000..685600cf --- /dev/null +++ b/vendor/mynah-ui/src/styles/_scss-variables.scss @@ -0,0 +1,18 @@ +// Global scss vars + +/** +* Status +*/ +$mynah-statuses: ('success', 'error', 'warning', 'info'); + +/** + * Padding sizes + */ +$mynah-padding-sizes: ( + 'none': 0, + 'small': 1, + 'medium': 3, + 'large': 4, +); + +$mynah-code-blocks-wrap-under: 300px; diff --git a/vendor/mynah-ui/src/styles/_splash-loader.scss b/vendor/mynah-ui/src/styles/_splash-loader.scss new file mode 100644 index 00000000..72be7c50 --- /dev/null +++ b/vendor/mynah-ui/src/styles/_splash-loader.scss @@ -0,0 +1,99 @@ +@import './scss-variables'; +.mynah-ui-splash-loader-wrapper { + transition: var(--mynah-short-rev-transition); + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: auto; + height: auto; + opacity: 0; + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + overflow: hidden; + z-index: calc(var(--mynah-z-max) * 2); + gap: var(--mynah-sizing-3); + &, + & * { + pointer-events: none; + } + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--mynah-color-bg); + opacity: 0.85; + z-index: 1; + } + + &.visible { + opacity: 1; + &, + & * { + pointer-events: all; + cursor: wait; + } + } + &:not(.visible) { + *, + *:before { + animation: none !important; + } + } + + > * { + z-index: 2; + } + > .mynah-ui-splash-loader-container { + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + overflow: hidden; + gap: var(--mynah-sizing-3); + > .mynah-ui-icon { + color: var(--mynah-color-status-info); + } + > .mynah-ui-splash-loader-text { + &:empty { + display: none; + } + font-size: var(--mynah-font-size-medium); + color: var(--mynah-color-text-default); + } + > .mynah-ui-spinner-container { + width: min(30vw, 30vh, 200px); + height: min(30vw, 30vh, 200px); + > .mynah-ui-spinner-logo-part.backdrop { + filter: blur(30px); + } + > .mynah-ui-spinner-logo-part.semi-backdrop::before { + filter: blur(30px); + } + } + } + + > .mynah-ui-splash-loader-buttons { + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + overflow: hidden; + gap: var(--mynah-sizing-3); + &:empty { + display: none; + } + > .mynah-button, + > .mynah-button * { + pointer-events: all; + cursor: pointer; + } + } +} diff --git a/vendor/mynah-ui/src/styles/_variables.scss b/vendor/mynah-ui/src/styles/_variables.scss new file mode 100644 index 00000000..d329b737 --- /dev/null +++ b/vendor/mynah-ui/src/styles/_variables.scss @@ -0,0 +1,183 @@ +:root { + --mynah-font-family: var(--vscode-font-family), system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + 'Amazon Ember', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: var(--vscode-font-size, 13px); + font-family: var(--mynah-font-family, 'system-ui'); + --mynah-syntax-code-font-family: var(--vscode-editor-font-family); + + // ======= + // Sizings + // ======= + + --mynah-max-width: 2560px; + --mynah-sizing-base: 4px; + --mynah-chat-wrapper-spacing: var(--mynah-sizing-4); + + --mynah-button-border-width: 1px; + --mynah-border-width: 1px; + + // Dropdown component sizing + --mynah-dropdown-width: calc(var(--mynah-sizing-base) * 50); // 250px (250/4 = 62.5) + --mynah-dropdown-max-height: calc(var(--mynah-sizing-base) * 50); // 300px (300/4 = 75) + --mynah-dropdown-min-width: calc(var(--mynah-sizing-base) * 40); // 200px (200/4 = 50) + --mynah-dropdown-option-min-height: var(--mynah-sizing-6); + --mynah-dropdown-margin: var(--mynah-sizing-1); + + // ====== + // Colors + // ====== + + --mynah-color-text-default: var(--vscode-foreground); // OK + --mynah-color-text-alternate: var(--vscode-foreground, var(--mynah-color-button-reverse)); + --mynah-color-text-strong: var(--vscode-input-foreground); + --mynah-color-text-weak: var( + --vscode-panelTitle-inactiveForeground, + var(--vscode-descriptionForeground, var(--vscode-disabledForeground)) + ); + --mynah-color-text-disabled: var(--vscode-disabledForeground, var(--mynah-color-text-weak)); + --mynah-color-text-link: var(--vscode-textLink-foreground); + --mynah-color-text-link-alternate: var(--mynah-color-button-reverse); + --mynah-color-text-input-border: var(--mynah-color-bg); + --mynah-color-text-input-border-focused: var(--mynah-color-border-default); + --mynah-color-text-input: var(--vscode-input-foreground); + --mynah-color-text-input-placeholder: var(--vscode-input-placeholderForeground, var(--mynah-color-text-disabled)); + + --mynah-color-bg: var(--vscode-sideBar-background, var(--vscode-sideBar-background)); + --mynah-color-tab-active: var(--vscode-tab-activeBackground, var(--vscode-editor-background, var(--mynah-card-bg))); + --mynah-color-light: rgba(0, 0, 0, 0.05); + --mynah-color-border-default: var(--vscode-panel-border, var(--vscode-tab-border, rgba(0, 0, 0, 0.1))); + --mynah-color-highlight: var(--vscode-editor-symbolHighlightBackground, #fff3d6); + --mynah-color-highlight-text: #886411; + --mynah-color-toggle: var(--vscode-sideBar-background); + --mynah-color-toggle-reverse: rgba(0, 0, 0, 0.5); + + --mynah-color-syntax-bg: var(--vscode-editor-background, #fafafa); + --mynah-color-syntax-variable: var(--vscode-debugTokenExpression-number, #986801); + --mynah-color-syntax-function: var(--vscode-debugTokenExpression-boolean, #e45649); + --mynah-color-syntax-property: var(--vscode-terminal-ansiCyan, #0184bb); + --mynah-color-syntax-operator: var(--vscode-terminal-foreground, #4078f2); + --mynah-color-syntax-comment: var(--vscode-debugConsole-sourceForeground, #a0a1a7); + --mynah-color-syntax-code: var(--vscode-editor-foreground, var(--mynah-color-text-default, #383a42)); + --mynah-color-syntax-keyword: var(--vscode-debugTokenExpression-name, #a626a4); + --mynah-color-syntax-string: var(--vscode-debugTokenExpression-string, #50a14f); + --mynah-color-syntax-class-name: var(--vscode-gitDecoration-modifiedResourceForeground, #c18401); + --mynah-color-syntax-deletion: rgba(255, 0, 0, 0.1); + --mynah-color-syntax-addition: rgba(0, 255, 128, 0.1); + + --mynah-color-status-info: #0971d3; + --mynah-color-status-success: #037f03; + --mynah-color-status-warning: #b2911c; + --mynah-color-status-error: var(--vscode-editorError-foreground, #d91515); + + --mynah-color-gradient-alternate: #ffffff; + + --mynah-color-gradient-start: #7638fa; + --mynah-color-gradient-mid: #3e8dff; + --mynah-color-gradient-end: #8af3ff; + + --mynah-bg-gradient-mid: #538bf7; + --mynah-bg-gradient-start: #301673; + --mynah-bg-gradient-end: #6e3bf1; + --mynah-bg-gradient-next: #da8af7; + --mynah-bg-gradient-prev: #0096fa; + + --mynah-bg-gradient: radial-gradient(100% 100% at 0% 0%, var(--mynah-bg-gradient-start) 0%, rgba(0, 0, 0, 0) 100%), + radial-gradient(100% 100% at 100% 100%, var(--mynah-bg-gradient-mid) 0%, rgba(0, 0, 0, 0) 100%), + radial-gradient(97.99% 114.79% at 108.09% -8.09%, var(--mynah-bg-gradient-end) 0%, rgba(0, 0, 0, 0) 100%), + var(--mynah-bg-gradient-end); + + --mynah-color-gradient-semi-transparent: linear-gradient( + 35deg, + var(--mynah-color-gradient-start), + var(--mynah-color-gradient-mid), + rgba(0, 0, 0, 0), + rgba(0, 0, 0, 0), + var(--mynah-color-gradient-mid), + var(--mynah-color-gradient-start) + ); + --mynah-color-gradient-main: linear-gradient( + 35deg, + var(--mynah-color-gradient-start), + var(--mynah-color-gradient-mid), + var(--mynah-color-gradient-end), + var(--mynah-color-gradient-start) + ); + --mynah-color-gradient-input: linear-gradient( + 35deg, + var(--vscode-inputOption-activeBorder, var(--mynah-color-gradient-start)), + var(--vscode-inputOption-activeBorder, var(--mynah-color-gradient-mid)), + var(--vscode-inputOption-activeBorder, var(--mynah-color-gradient-end)), + var(--vscode-inputOption-activeBorder, var(--mynah-color-gradient-start)) + ); + --mynah-color-gradient-main-half: linear-gradient( + 90deg, + var(--mynah-color-gradient-start), + var(--mynah-color-gradient-mid) + ); + + --mynah-color-button: var(--vscode-button-background); + --mynah-color-button-reverse: var(--vscode-button-foreground); + --mynah-color-button-primary: var(--vscode-button-background, var(--mynah-color-button)); + --mynah-color-button-primary-hover: var(--vscode-button-hoverBackground, var(--mynah-color-button)); + --mynah-color-button-primary-light: var(--vscode-list-activeSelectionBackground, rgba(0, 122, 255, 0.1)); + --mynah-color-alternate: var(--vscode-button-secondaryBackground); + --mynah-color-alternate-reverse: var(--vscode-button-secondaryForeground); + --mynah-input-bg: var(--vscode-input-background, var(--mynah-card-bg)); + --mynah-card-bg: var(--vscode-editor-background); + --mynah-card-bg-alternate: var(--vscode-editor-background, var(--mynah-color-button)); // OK + + --mynah-shadow-button: none; + --mynah-shadow-card: none; + --mynah-shadow-card-hover: 0 40px 25px -15px rgba(0, 0, 0, 0.35); + --mynah-shadow-card-border: 0 2px 8px rgba(0, 0, 0, 0.08); + --mynah-shadow-overlay: 0 0px 15px -5px rgba(0, 0, 0, 0.4); + --mynah-drag-overlay-blur-bg: rgba(136, 136, 136, 0.2); + // ========== + // Font sizes + // ========== + + --mynah-font-size-xxsmall: 0.825rem; + --mynah-font-size-xsmall: 0.875rem; + --mynah-font-size-small: 0.925rem; + --mynah-font-size-medium: 1rem; + --mynah-font-size-large: 1.125rem; + --mynah-font-size-xlarge: 1.25rem; + --mynah-font-size-xxlarge: 1.4rem; + --mynah-line-height: 1.5rem; + + --mynah-syntax-code-block-max-height: calc(25 * var(--mynah-syntax-code-font-size) * 118 / 100); + --mynah-syntax-code-line-height: 118%; + --mynah-syntax-code-font-size: var(--vscode-editor-font-size, var(--mynah-font-size-medium)); + + --mynah-card-radius: var(--mynah-sizing-1); + --mynah-input-radius: var(--mynah-sizing-1); + --mynah-card-radius-corner: 0; + --mynah-button-radius: var(--mynah-sizing-1); + + // =========== + // Transitions + // =========== + + --mynah-main-wrapper-transition: all 350ms cubic-bezier(0.83, 0, 0.17, 1); + --mynah-bottom-panel-transition: all 450ms cubic-bezier(0.25, 1, 0, 1); + --mynah-short-rev-transition: all 280ms cubic-bezier(0.35, 1, 0, 1); + --mynah-very-short-transition: all 300ms cubic-bezier(0.25, 1, 0, 1); + --mynah-very-long-transition: all 1000ms cubic-bezier(0.25, 1, 0, 1); + --mynah-short-transition: all 300ms cubic-bezier(0.85, 0.15, 0, 1); + --mynah-short-transition-rev: all 280ms cubic-bezier(0.35, 1, 0, 1); + --mynah-short-transition-rev-opacity: opacity 350ms cubic-bezier(0.35, 1, 0, 1); + --mynah-text-flow-transition: all 400ms cubic-bezier(0.35, 1, 0, 1), transform 400ms cubic-bezier(0.25, 1, 0, 1); + + // ========= + // Z-indices + // ========= + + --mynah-z-sub: -1; /* Below default layer */ + --mynah-z-0: 0; /* Default state */ + --mynah-z-1: 100; /* First layer */ + --mynah-z-2: 200; /* Second layer */ + --mynah-z-3: 300; /* Third layer */ + --mynah-z-4: 400; /* Fourth layer */ + --mynah-z-5: 500; /* Fifth layer */ + --mynah-z-max: 999; /* Maximum layer - for critical elements */ +} diff --git a/vendor/mynah-ui/src/styles/components/_background.scss b/vendor/mynah-ui/src/styles/components/_background.scss new file mode 100644 index 00000000..04b80212 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_background.scss @@ -0,0 +1,63 @@ +@import '../scss-variables'; +.mynah-ui-gradient-background { + transition: var(--mynah-short-rev-transition); + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: auto; + height: auto; + background-repeat: no-repeat; + background-position: bottom center; + background-size: cover; + opacity: 1; + pointer-events: none; + + display: flex; + justify-content: center; + align-items: flex-end; + overflow: hidden; + z-index: var(--mynah-z-0); + background-color: var(--mynah-color-bg); + + &:before, + &:after { + content: ''; + position: absolute; + left: 0; + bottom: 0; + top: 0; + width: 100%; + height: auto; + top: 0; + background-repeat: no-repeat; + background-position: bottom center; + background-size: cover; + z-index: var(--mynah-z-2); + } + &:before { + background-image: linear-gradient( + to bottom, + var(--mynah-color-bg) 0, + var(--mynah-color-bg) 40%, + transparent 100% + ); + mask-image: linear-gradient(to top, transparent 0%, black 35%, black 100%); + -webkit-mask-image: linear-gradient(to top, transparent 0%, black 35%, black 100%); + } + &:after { + top: initial; + height: 400px; + background-image: linear-gradient(to top, var(--mynah-color-bg) 40px, transparent 100%); + mask-image: linear-gradient(to bottom, transparent 0, black 100%); + -webkit-mask-image: linear-gradient(to bottom, transparent 0, black 100%); + } + + > svg { + min-width: 100vw; + z-index: var(--mynah-z-1); + min-height: 200vw; + opacity: 0.75; + } +} diff --git a/vendor/mynah-ui/src/styles/components/_button.scss b/vendor/mynah-ui/src/styles/components/_button.scss new file mode 100644 index 00000000..63f85a58 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_button.scss @@ -0,0 +1,202 @@ +@import '../scss-variables'; +.mynah-button { + &:not(.mynah-button-border) { + border: none; + } + min-width: calc(var(--mynah-sizing-6) + var(--mynah-sizing-half)); + min-height: calc(var(--mynah-sizing-6) + var(--mynah-sizing-half)); + border-radius: var(--mynah-button-radius); + font-family: var(--mynah-font-family); + font-size: var(--mynah-font-size-medium) !important; + cursor: pointer; + transition: var(--mynah-very-short-transition); + display: inline-flex; + justify-content: center; + align-items: center; + outline: none; + overflow: hidden; + position: relative; + transform: translateZ(0px); + padding: var(--mynah-sizing-half) calc(var(--mynah-sizing-1) + var(--mynah-sizing-half)); + gap: var(--mynah-sizing-1); + opacity: 1; + background-color: transparent; + color: inherit; + &:not(.mynah-button-secondary) { + &.fill-state-always, + &.fill-state-hover:hover { + box-shadow: var(--mynah-shadow-button); + color: var(--mynah-color-button-reverse); + background-color: var(--mynah-color-button); + } + } + &::after { + content: ''; + pointer-events: none; + transition: var(--mynah-very-short-transition); + opacity: 0; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + border-radius: inherit; + background-color: currentColor; + transform: translate3d(-7%, 0, 0) scale(0.93); + } + &[disabled='disabled'] { + opacity: 0.25 !important; + cursor: default; + } + &.mynah-button-secondary { + background-color: rgba(0, 0, 0, 0); + color: currentColor; + box-sizing: border-box; + &.mynah-button-border { + border: var(--mynah-border-width) solid currentColor !important; + } + box-shadow: none; + @each $status in $mynah-statuses { + &.status-#{$status} { + &::before { + content: ''; + position: absolute; + width: 100%; + height: 100%; + opacity: 0.08; + } + &.fill-state-always, + &.fill-state-hover:hover { + &::before { + background-color: var(--mynah-color-status-#{$status}); + } + } + &::after { + color: var(--mynah-color-status-#{$status}); + } + .mynah-button-border { + border: var(--mynah-border-width) solid var(--mynah-color-status-#{$status}) !important; + } + } + } + } + &.status-dimmed-clear, + &.status-clear { + &, + &.mynah-button-border { + border-color: transparent !important; + } + &::before { + background-color: rgba(0, 0, 0, 0) !important; + } + } + &.status-dimmed-clear { + color: var(--mynah-color-text-weak); + } + &.status-main { + &::before { + transition: inherit; + content: ''; + position: absolute; + width: 100%; + height: 100%; + opacity: 0; + background-image: var(--mynah-color-gradient-main-half); + background-repeat: no-repeat; + background-size: 105% 105%; + background-position: center center; + } + &.fill-state-hover { + border: var(--mynah-border-width) solid currentColor; + } + .mynah-chat-item-card:hover &.mynah-button-flash-by-parent-focus:not(:hover), + &.fill-state-always, + &.fill-state-hover:hover { + border-color: rgba(0, 0, 0, 0) !important; + color: var(--mynah-color-gradient-alternate); + &::before { + opacity: 1; + } + } + } + &:active, + &:focus-visible { + &::before { + opacity: 0.6 !important; + } + } + &:hover { + &::after { + transform: translate3d(0%, 0, 0); + opacity: 0.15; + } + } + &.mynah-icon-button { + width: var(--mynah-sizing-10); + height: var(--mynah-sizing-10); + border-radius: 0; + } + > span { + white-space: nowrap; + transition: inherit; + overflow: hidden; + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; + z-index: var(--mynah-z-1); + } + + .mynah-chat-item-card:hover &.mynah-button-flash-by-parent-focus:not(:hover) { + &, + &:after { + animation: flash-main 3.25s cubic-bezier(0.75, 0, 0.25, 1) infinite both; + } + animation-name: flash-main; + &:after { + animation-name: flash-pseudo; + } + &.animate-once { + &, + &:after { + animation-iteration-count: 1; + } + } + } + + &.hidden { + display: none !important; + visibility: hidden !important; + pointer-events: none !important; + } +} + +.mynah-button-confirmation-dialog-container { + padding: var(--mynah-sizing-3); + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-1); + max-width: 90vw; + > .mynah-button-confirmation-dialog-header { + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + gap: var(--mynah-sizing-2); + > i:first-child { + color: var(--mynah-color-status-warning); + flex-shrink: 0; + } + > h4:nth-child(2) { + margin: 0; + flex: 1; + } + } + > .mynah-button-confirmation-dialog-buttons { + display: flex; + flex-flow: row nowrap; + justify-content: flex-end; + align-items: center; + gap: var(--mynah-sizing-2); + } +} diff --git a/vendor/mynah-ui/src/styles/components/_collapsible-content.scss b/vendor/mynah-ui/src/styles/components/_collapsible-content.scss new file mode 100644 index 00000000..21dd996f --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_collapsible-content.scss @@ -0,0 +1,48 @@ +.mynah-collapsible-content-wrapper { + display: block; + box-sizing: border-box; + width: 100%; + overflow: hidden; + position: relative; + > .mynah-collapsible-content-checkbox { + display: none; + & + .mynah-collapsible-content-label { + box-sizing: border-box; + display: flex; + flex-flow: column nowrap; + overflow: hidden; + align-items: flex-start; + justify-content: stretch; + padding: var(--mynah-sizing-2); + gap: var(--mynah-sizing-2); + > .mynah-collapsible-content-label-title-wrapper { + gap: var(--mynah-sizing-2); + display: flex; + flex-flow: row nowrap; + box-sizing: border-box; + width: 100%; + overflow: hidden; + justify-content: flex-start; + align-items: flex-start; + pointer-events: all; + cursor: pointer; + user-select: none; + > .mynah-collapsible-content-label-title-text { + flex: 1; + overflow: hidden; + } + } + > .mynah-collapsible-content-label-content-wrapper { + overflow: hidden; + box-sizing: border-box; + width: 100%; + } + } + + &:checked + .mynah-collapsible-content-label { + > .mynah-collapsible-content-label-content-wrapper { + display: none; + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_detailed-list.scss b/vendor/mynah-ui/src/styles/components/_detailed-list.scss new file mode 100644 index 00000000..13c49f6c --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_detailed-list.scss @@ -0,0 +1,370 @@ +@import '../mixins'; +.mynah-detailed-list { + display: flex; + box-sizing: border-box; + width: 100%; + flex-flow: column nowrap; + align-items: stretch; + justify-content: flex-start; + overflow: hidden; + gap: var(--mynah-sizing-2); + pointer-events: all; + flex-shrink: 0; + + > .mynah-detailed-list-header-wrapper { + flex-shrink: 0; + + > .mynah-ui-title-description-icon-wrapper { + > .mynah-ui-title-description-icon-icon { + font-size: var(--mynah-font-size-large); + color: var(--mynah-color-text-strong); + } + > .mynah-ui-title-description-icon-title { + font-size: var(--mynah-font-size-large); + color: var(--mynah-color-text-strong); + font-weight: 600; + } + > .mynah-ui-title-description-icon-description { + font-size: var(--mynah-font-size-medium); + color: var(--mynah-color-text-weak); + } + } + &:empty { + display: none; + } + } + > .mynah-detailed-list-filters-wrapper { + flex-shrink: 0; + &:empty { + display: none; + } + > .mynah-chat-item-form-items-container { + gap: var(--mynah-sizing-4); + } + } + > .mynah-detailed-list-filter-actions-wrapper { + align-self: flex-end; + } + > .mynah-detailed-list-item-groups-wrapper { + flex: 1; + display: flex; + box-sizing: border-box; + width: 100%; + pointer-events: all; + flex-flow: column nowrap; + align-items: stretch; + justify-content: flex-start; + overflow-x: hidden; + overflow-y: auto; + &:empty { + display: none; + } + + > .mynah-detailed-list-group { + display: flex; + box-sizing: border-box; + width: 100%; + flex-flow: column nowrap; + align-items: stretch; + justify-content: flex-start; + gap: var(--mynah-border-width); + font-size: var(--mynah-font-size-medium); + background-color: inherit; + > .mynah-detailed-list-group-title { + margin: 0; + color: var(--mynah-color-text-strong); + padding: 0 var(--mynah-sizing-3); + margin-bottom: var(--mynah-sizing-1); + position: relative; + border-radius: var(--mynah-input-radius); + overflow: hidden; + display: flex; + align-items: center; + gap: var(--mynah-sizing-2); + + &.mynah-group-title-clickable { + cursor: pointer; + + &:hover { + background-color: var(--mynah-color-button); + color: var(--mynah-color-button-reverse); + + * { + color: var(--mynah-color-button-reverse); + } + } + } + + > .mynah-card-body { + flex: 1; + * { + margin: 0; + padding-bottom: 0 !important; + } + } + > .mynah-chat-item-buttons-container > .mynah-button { + height: var(--mynah-sizing-6); + min-width: var(--mynah-sizing-6); + padding: var(--mynah-sizing-1); + } + } + + & + .mynah-detailed-list-group { + margin-top: var(--mynah-sizing-2); + padding-top: var(--mynah-sizing-4); + border-top: var(--mynah-border-width) solid var(--mynah-color-border-default); + } + + > .mynah-detailed-list-items-block { + display: block; + overflow: hidden; + min-height: var(--mynah-sizing-8); + + &.indented { + padding-left: var(--mynah-sizing-3); + } + + > .mynah-detailed-list-item { + display: flex; + gap: var(--mynah-sizing-half); + position: relative; + box-sizing: border-box; + width: 100%; + flex-flow: row nowrap; + align-items: center; + min-height: var(--mynah-sizing-8); + justify-content: flex-start; + overflow: hidden; + padding: var(--mynah-sizing-1) var(--mynah-sizing-2); + color: var(--mynah-color-text-default); + border-radius: var(--mynah-input-radius); + background-color: inherit; + margin-bottom: var(--mynah-sizing-half); + + &[selectable='true'], + &[clickable='true'] { + cursor: pointer; + } + + &[disabled='true'] { + &::before { + border-color: transparent !important; + } + opacity: 0.5; + &, + & * { + pointer-events: none; + } + } + &:hover:not([disabled='true']):not([selectable='false']), + &:hover:not([disabled='true'])[clickable='true'], + &.target-command:not([disabled='true']):not([selectable='false']) { + background-color: var(--mynah-color-button); + &, + & * { + color: var(--mynah-color-button-reverse); + .mynah-detailed-list-item-description { + opacity: 0.65; + } + } + } + > .mynah-detailed-list-icon { + flex-shrink: 0; + padding-right: var(--mynah-sizing-2); + > .mynah-ui-icon { + font-size: var(--mynah-font-size-large); + } + color: var(--mynah-color-text-default); + } + + > .mynah-detailed-list-item-text { + display: flex; + gap: var(--mynah-sizing-half); + position: relative; + box-sizing: border-box; + width: 100%; + flex-flow: row nowrap; + align-items: flex-start; + justify-content: flex-start; + overflow: hidden; + + &.mynah-detailed-list-item-text-direction-column { + flex-direction: column; + gap: var(--mynah-sizing-2); + + > .mynah-detailed-list-item-name { + width: 100%; + } + + > .mynah-detailed-list-item-description { + width: 100%; + } + } + + > .mynah-detailed-list-item-name { + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-family: var(--mynah-font-family); + font-weight: bold; + > mark { + position: relative; + color: initial !important; + overflow: hidden; + } + + & + .mynah-detailed-list-item-description { + flex-grow: 1; + flex-shrink: 1; + color: var(--mynah-color-text-weak); + display: flex; + overflow: hidden; + justify-content: flex-start; + display: inline-block; + white-space: nowrap; + text-align: left; + text-overflow: ellipsis; + padding-left: var(--mynah-sizing-1); + padding-right: var(--mynah-sizing-1); + + p { + margin: 0; + } + } + } + + > .mynah-detailed-list-item-description { + flex-grow: 1; + flex-shrink: 1; + display: flex; + overflow: hidden; + justify-content: flex-start; + display: inline-block; + white-space: nowrap; + text-align: left; + text-overflow: ellipsis; + margin: 0; + &.rtl { + direction: rtl; + text-align: left; + } + } + } + + > .mynah-detailed-list-item-status { + flex-shrink: 0; + display: inline-flex; + flex-flow: row nowrap; + gap: var(--mynah-sizing-1); + justify-content: flex-start; + align-items: center; + font-size: var(--mynah-font-size-large); + padding-right: var(--mynah-sizing-1); + min-height: var(--mynah-sizing-7); + @each $status in $mynah-statuses { + &.status-#{$status} { + color: var(--mynah-color-status-#{$status}); + } + } + > span { + font-size: var(--mynah-font-size-small); + } + } + > .mynah-detailed-list-item-actions { + flex-shrink: 0; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-end; + + > .mynah-detailed-list-item-actions-item { + padding: 0 var(--mynah-sizing-half); + & + .mynah-detailed-list-item-actions-item:not(:last-child) { + border-left: var(--mynah-border-width) solid var(--mynah-color-border-default); + } + } + } + + > .mynah-detailed-list-item-arrow-icon { + flex-shrink: 0; + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-small); + } + + > .mynah-detailed-list-item-disabled-text { + flex-shrink: 0; + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-small); + font-style: italic; + white-space: nowrap; + } + } + } + } + } + + &:hover + > .mynah-detailed-list-item-groups-wrapper + > .mynah-detailed-list-group + > .mynah-detailed-list-items-block + > .mynah-detailed-list-item { + &.target-command:not([disabled='true']):not([selectable='false']):not(:hover) { + background-color: inherit; + &:before { + content: ''; + position: absolute; + top: 0; + left: 0; + box-sizing: border-box; + width: 100%; + height: 100%; + border: solid var(--mynah-button-border-width) var(--mynah-color-button); + border-radius: inherit; + } + &, + & * { + color: var(--mynah-color-text-default); + } + } + } +} + +.mynah-detailed-list-item-actions-overlay { + display: inline-flex; + flex-flow: column nowrap; + align-items: flex-start; + justify-content: flex-start; + gap: var(--mynah-sizing-1); + overflow: hidden; + position: relative; + pointer-events: all; + padding: var(--mynah-sizing-4); + border-radius: inherit; + button { + pointer-events: all; + > .mynah-button-label { + padding: 0; + flex-shrink: 0; + } + > i { + flex-shrink: 0; + & + .mynah-button-label { + padding-left: var(--mynah-sizing-1); + flex-shrink: 0; + } + } + } +} + +.mynah-sheet + > .mynah-sheet-body + > .mynah-detailed-list + > .mynah-detailed-list-item-groups-wrapper + > .mynah-detailed-list-group { + > .mynah-detailed-list-group-title, + > .mynah-detailed-list-items-block > .mynah-detailed-list-item[selectable='false'] { + padding-left: 0; + padding-right: 0; + } +} diff --git a/vendor/mynah-ui/src/styles/components/_dropdown-list.scss b/vendor/mynah-ui/src/styles/components/_dropdown-list.scss new file mode 100644 index 00000000..5fe917a8 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_dropdown-list.scss @@ -0,0 +1,180 @@ +.mynah-dropdown-list-wrapper { + position: relative; + width: auto; + display: inline-block; + margin-bottom: var(--mynah-sizing-2); + + .mynah-dropdown-list-button { + display: flex; + justify-content: space-between; + align-items: center; + padding: var(--mynah-sizing-1) var(--mynah-sizing-1) var(--mynah-sizing-1) var(--mynah-sizing-2); + border: none; + border-radius: var(--mynah-card-radius); + cursor: pointer; + transition: var(--mynah-very-short-transition); + + .mynah-button-label { + flex: 1; + font-size: var(--mynah-font-size-xsmall); + text-align: right; + flex-shrink: 0; + margin-left: auto; + } + } +} + +// Portal-based dropdown content (rendered at document root) +.mynah-dropdown-list-portal { + position: fixed; + .mynah-dropdown-list-content { + width: var(--mynah-dropdown-width); + max-height: var(--mynah-dropdown-max-height); + overflow-y: auto; + margin-top: var(--mynah-sizing-1); + background-color: var(--mynah-card-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + box-shadow: var(--mynah-shadow-overlay); + border-radius: var(--mynah-card-radius); + + .mynah-dropdown-list-footer { + padding: var(--mynah-sizing-1) var(--mynah-sizing-2); + + .mynah-dropdown-list-description { + font-size: var(--mynah-font-size-xsmall); + color: var(--mynah-color-text-weak); + margin: 0; + padding: 0; + // Align with option text by accounting for checkbox space + margin-left: calc(var(--mynah-sizing-3) + var(--mynah-sizing-2)); + + .mynah-dropdown-list-description-link { + color: var(--mynah-color-button-primary); + background: none; + border: none; + padding: 0; + cursor: pointer; + font-size: inherit; + text-align: left; + font-family: inherit; + text-decoration: underline; + + &:hover { + color: var(--mynah-color-button-primary-hover); + } + + &:focus { + outline: var(--mynah-border-width) solid var(--mynah-color-button-primary); + outline-offset: 1px; + } + + // Add margin only when there's text before the link + &:not(:first-child) { + margin-left: var(--mynah-sizing-half); + } + } + } + } + + .mynah-dropdown-list-options { + overflow-y: auto; + + .mynah-dropdown-list-option { + display: flex; + align-items: center; + padding: var(--mynah-sizing-1) var(--mynah-sizing-2); + cursor: pointer; + min-height: var(--mynah-sizing-4); // Consistent height to prevent text shift + position: relative; + overflow: hidden; + transition: var(--mynah-very-short-transition); + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + background-color: currentColor; + pointer-events: none; + transition: var(--mynah-very-short-transition); + } + + &::after { + content: ''; + pointer-events: none; + transition: var(--mynah-very-short-transition); + opacity: 0; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: currentColor; + transform: translate3d(-7%, 0, 0) scale(0.93); + } + + &:hover { + &::after { + transform: translate3d(0%, 0, 0); + opacity: 0.15; + } + } + + &:focus-visible { + outline: var(--mynah-border-width) solid var(--mynah-color-button-primary); + outline-offset: -1px; + &::before { + opacity: 0.6; + } + } + + &[aria-selected='true'] { + background-color: var(--mynah-color-button-primary-light); + } + + .mynah-dropdown-list-checkbox { + display: flex; + align-items: center; + justify-content: center; + margin-right: var(--mynah-sizing-2); + width: var(--mynah-sizing-3); + height: var(--mynah-sizing-3); + + .mynah-dropdown-list-check-icon { + color: var(--mynah-color-text-default); + font: 700; + width: calc(var(--mynah-sizing-2) + var(--mynah-sizing-half)); + height: calc(var(--mynah-sizing-2) + var(--mynah-sizing-half)); + display: flex; + align-items: center; + justify-content: center; + } + } + + .mynah-dropdown-list-option-label { + flex: 1; + font-size: var(--mynah-font-size-small); + color: var(--mynah-color-text-default); + display: flex; + align-items: center; + min-height: var(--mynah-sizing-4); // Prevent text shift + + .mynah-dropdown-list-check-icon { + color: var(--mynah-color-text-default); + font-weight: bold; + width: var(--mynah-sizing-4); + height: var(--mynah-sizing-4); + display: flex; + align-items: center; + justify-content: center; + margin-right: var(--mynah-sizing-1); + flex-shrink: 0; // Prevent icon from shrinking + } + } + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_form-input.scss b/vendor/mynah-ui/src/styles/components/_form-input.scss new file mode 100644 index 00000000..8610da86 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_form-input.scss @@ -0,0 +1,220 @@ +.mynah-form-input-wrapper { + position: relative; + display: flex; + box-sizing: border-box; + gap: var(--mynah-sizing-2); + flex-flow: column nowrap; + pointer-events: all; + > .mynah-form-input-label { + &:empty { + display: none; + } + > .mynah-ui-form-item-bold-label { + font-weight: bold; + } + > .mynah-ui-form-item-mandatory-title { + display: inline-flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + gap: var(--mynah-sizing-1); + > .mynah-ui-icon { + color: var(--mynah-color-status-warning); + opacity: 0.75; + font-size: 75%; + } + } + } + .mynah-ui-form-item-description { + font-size: var(--mynah-font-size-small); + color: var(--mynah-color-text-weak); + } + .mynah-form-input-container { + position: relative; + display: flex; + box-sizing: border-box; + justify-content: flex-end; + align-items: center; + z-index: var(--mynah-z-0); + min-width: calc(var(--mynah-sizing-6) + var(--mynah-sizing-half)); + min-height: calc(var(--mynah-sizing-6) + var(--mynah-sizing-half)); + + &:not(.no-border) { + padding: var(--mynah-sizing-2); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + background-color: var(--mynah-card-bg); + border-radius: var(--mynah-input-radius); + transform: translateZ(0px); + } + + &:has(.mynah-form-input:disabled) { + &::after { + content: ''; + position: absolute; + top: calc(var(--mynah-border-width) * -1); + left: calc(var(--mynah-border-width) * -1); + right: calc(var(--mynah-border-width) * -1); + bottom: calc(var(--mynah-border-width) * -1); + background-color: var(--mynah-color-text-disabled); + border-radius: var(--mynah-input-radius); + z-index: var(--mynah-z-sub); + opacity: 10%; + } + } + + > .mynah-form-input { + width: 100%; + left: 0; + color: var(--mynah-color-text-default); + background-color: transparent; + + &[disabled] { + pointer-events: none; + opacity: 50%; + } + + &::placeholder { + color: var(--mynah-color-text-input-placeholder); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + option { + background-color: var(--mynah-card-bg); + } + option.empty-option { + font-style: italic; + opacity: 0.5; + } + + -webkit-appearance: none; + appearance: none; + border: none; + text-overflow: clip; + outline: none; + + > { + @import './form-items/radio-group'; + } + + > .mynah-feedback-form-stars-container { + transition: var(--mynah-short-transition-rev); + transform-origin: right bottom; + display: inline-flex; + flex-flow: row nowrap; + > .mynah-feedback-form-star { + cursor: pointer; + padding-right: var(--mynah-sizing-1); + color: var(--mynah-color-button); + font-size: 1.5rem; + transition: var(--mynah-very-short-transition); + > .mynah-ui-icon { + transition: var(--mynah-very-short-transition); + opacity: 0.4; + transform: translate3d(0, 0, 0) scale(0.6); + } + } + &[selected-star]:not(:hover) > :not(.mynah-feedback-form-star.selected ~ .mynah-feedback-form-star), + &:hover > :not(.mynah-feedback-form-star:hover ~ .mynah-feedback-form-star) { + > .mynah-ui-icon { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1); + } + } + } + + &.validation-error { + border-color: var(--mynah-color-status-error); + } + } + + select.mynah-form-input { + padding: 0px var(--mynah-sizing-2); + } + + > .select-auto-width-sizer { + opacity: 0; + color: transparent; + padding-right: calc(var(--mynah-sizing-2) + 1em); + & + select { + position: absolute; + left: 0; + width: 100% !important; + cursor: pointer; + } + } + + &.mynah-form-input-toggle-group { + justify-content: flex-start; + > .mynah-form-input { + width: auto; + // padding: var(--mynah-sizing-half); + border-radius: var(--mynah-input-radius); + background-color: var(--mynah-color-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + display: inline-flex; + flex-flow: row nowrap; + overflow: hidden; + gap: var(--mynah-sizing-half); + > { + @import './form-items/toggle-group'; + } + } + } + + > textarea.mynah-form-input { + font-family: var(--mynah-font-family); + resize: none; + font-size: var(--mynah-font-size-medium); + color: var(--mynah-color-text-input); + outline: none; + } + > .mynah-form-input-icon { + color: var(--mynah-color-text-weak); + } + > .mynah-select-handle { + position: relative; + color: var(--mynah-color-text-weak); + pointer-events: none; + transform: translateX(-25%); + } + & ~ .mynah-form-input-validation-error-block { + display: flex; + flex-flow: column nowrap; + font-size: var(--mynah-font-size-xsmall); + font-style: italic; + color: var(--mynah-color-status-error); + opacity: 0.75; + &:empty { + display: none; + } + } + } + + &[disabled='disabled'] { + > .mynah-form-item-list-wrapper { + > .mynah-button, + > .mynah-form-item-list-rows-wrapper > .mynah-form-item-list-row > .mynah-button { + display: none !important; + } + } + } +} + +.mynah-chat-item-form-items-container { + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-5); + overflow: hidden; + padding-bottom: var(--mynah-sizing-1); +} +.mynah-chat-item-buttons-container { + display: flex; + flex-flow: row-reverse wrap-reverse; + gap: var(--mynah-sizing-2); + overflow: hidden; + padding-top: 0; + padding-bottom: 0; + justify-content: flex-end; + align-items: center; +} diff --git a/vendor/mynah-ui/src/styles/components/_icon.scss b/vendor/mynah-ui/src/styles/components/_icon.scss new file mode 100644 index 00000000..16de2677 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_icon.scss @@ -0,0 +1,26 @@ +@import '../scss-variables'; + +.mynah-ui-icon { + font-style: normal; + font-weight: normal; + display: inline-flex; + width: 1em; + height: 1em; + font-variant: normal; + text-transform: none; + -webkit-mask: center/100% no-repeat; + mask: center/100% no-repeat; + color: inherit; + background-color: currentColor; + aspect-ratio: 1 / 1; + + > span { + display: none; + } + + @each $status in $mynah-statuses { + &.status-#{$status} { + color: var(--mynah-color-status-#{$status}); + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_main-container.scss b/vendor/mynah-ui/src/styles/components/_main-container.scss new file mode 100644 index 00000000..0e488396 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_main-container.scss @@ -0,0 +1,73 @@ +body.mynah-ui-root { + min-width: 100vw; + min-height: 100vh; +} + +#mynah-wrapper { + container-type: size; + container-name: mynah-wrapper; + display: flex; + flex-flow: column nowrap; + margin: 0 auto; + width: inherit; + min-width: inherit; + max-width: var(--mynah-max-width); + box-sizing: border-box; + height: 100%; + position: relative; + overflow: hidden; + justify-content: stretch; + align-items: stretch; + background-color: var(--mynah-color-bg); + color: var(--mynah-color-text-default); + + > .mynah-no-tabs-wrapper:not(.hidden) { + & + .mynah-ui-tab-contents-wrapper { + display: none; + } + } + > .mynah-ui-tab-contents-wrapper { + position: relative; + width: inherit; + min-width: inherit; + max-width: inherit; + display: block; + height: inherit; + // Flexbox fix + min-height: 0; + flex: 1; + + &:first-child { + > .mynah-chat-wrapper { + display: flex; + } + } + } + + @for $i from 1 through 6 { + h#{$i} { + font-weight: 600; + } + } + + ::-webkit-scrollbar { + width: 2px; + height: 2px; + opacity: 0.25; + &:horizontal { + width: 0px; + height: 0px; + } + } + + *:focus { + outline: none; + } +} + +@container mynah-wrapper (max-width: 0px) { + * { + display: none !important; + content-visibility: hidden !important; + } +} diff --git a/vendor/mynah-ui/src/styles/components/_more-content-indicator.scss b/vendor/mynah-ui/src/styles/components/_more-content-indicator.scss new file mode 100644 index 00000000..3899cf47 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_more-content-indicator.scss @@ -0,0 +1,21 @@ +.more-content-indicator { + position: relative; + padding: var(--mynah-border-width) var(--mynah-sizing-2); + pointer-events: none; + opacity: 1; + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + background-color: var(--mynah-color-bg); + box-shadow: 0 -15px 15px 0px var(--mynah-color-bg); + box-sizing: border-box; + height: var(--mynah-sizing-8); + min-height: var(--mynah-sizing-8); + max-height: var(--mynah-sizing-8); + margin-top: calc(-1 * (var(--mynah-sizing-8) + (2 * var(--mynah-border-width)))); + z-index: var(--mynah-z-4); + > .mynah-button { + pointer-events: all; + } +} diff --git a/vendor/mynah-ui/src/styles/components/_nav-tabs-buttons-wrapper.scss b/vendor/mynah-ui/src/styles/components/_nav-tabs-buttons-wrapper.scss new file mode 100644 index 00000000..1ccd3a24 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_nav-tabs-buttons-wrapper.scss @@ -0,0 +1,39 @@ +.mynah-nav-tabs-bar-buttons-wrapper { + border-left: var(--mynah-button-border-width) solid var(--mynah-color-border-default); + display: inline-flex; + align-items: center; + overflow: hidden; + z-index: var(--mynah-z-1); + min-height: var(--mynah-sizing-10); + max-height: var(--mynah-sizing-10); + position: relative; + flex-shrink: 0; + flex-grow: 1; + justify-content: flex-end; + &:empty { + display: none; + } +} +.mynah-nav-tabs-bar-buttons-wrapper-overlay { + display: inline-flex; + flex-flow: column nowrap; + align-items: flex-start; + justify-content: flex-start; + gap: var(--mynah-sizing-1); + overflow: hidden; + position: relative; + pointer-events: all; + padding: var(--mynah-sizing-4); + border-radius: inherit; + button { + pointer-events: all; + > .mynah-button-label { + padding: 0; + } + > i { + & + .mynah-button-label { + padding-left: var(--mynah-sizing-1); + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_nav-tabs.scss b/vendor/mynah-ui/src/styles/components/_nav-tabs.scss new file mode 100644 index 00000000..f2d5f843 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_nav-tabs.scss @@ -0,0 +1,69 @@ +.mynah-nav-tabs-wrapper { + border-top: var(--mynah-button-border-width) solid var(--mynah-color-border-default); + background-color: var(--mynah-color-bg); + display: flex; + align-items: center; + overflow: hidden; + z-index: var(--mynah-z-1); + min-height: var(--mynah-sizing-8); + max-height: var(--mynah-sizing-8); + position: relative; + &:after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: 0; + width: auto; + height: var(--mynah-button-border-width); + background-color: var(--mynah-color-border-default); + } + + > .mynah-tabs-container { + height: 100%; + background: none; + box-sizing: border-box; + > span { + white-space: nowrap; + } + } + &:empty { + display: none; + } + + &.mynah-nav-tabs-loading, + &.mynah-nav-tabs-loading * { + pointer-events: none; + } + .mynah-button { + margin: var(--mynah-border-width); + margin-top: 0; + } +} + +@import 'nav-tabs-buttons-wrapper'; + +.mynah-nav-tabs-max-reached-overlay { + min-width: max(20vw, 100px); + max-width: 90vw; +} + +.mynah-overlay > .mynah-overlay-container .mynah-card.mynah-nav-tabs-close-confirmation-overlay { + > .mynah-nav-tabs-close-confirmation-buttons-wrapper { + display: inline-flex; + flex-flow: row nowrap; + max-width: max-content; + gap: var(--mynah-sizing-3); + > .mynah-button { + pointer-events: all !important; + &.mynah-nav-tabs-close-confirmation-close-button { + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + color: var(--mynah-color-text-default); + background-color: transparent; + &:hover { + border-color: var(--mynah-color-text-default); + } + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_no-tabs.scss b/vendor/mynah-ui/src/styles/components/_no-tabs.scss new file mode 100644 index 00000000..b159d57e --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_no-tabs.scss @@ -0,0 +1,39 @@ +.mynah-no-tabs-wrapper { + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + gap: var(--mynah-sizing-6); + flex: 1; + &.hidden { + display: none; + } + &:not(.hidden) + .mynah-ui-tab-contents-wrapper { + display: none; + } + > .mynah-no-tabs-icon-wrapper { + > .mynah-ui-icon { + font-size: calc(2 * var(--mynah-sizing-18)); + color: var(--mynah-color-text-weak); + opacity: 0.15; + } + } + > .mynah-no-tabs-info { + > * { + margin: 1rem; + } + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-large); + opacity: 0.75; + text-align: center; + } + > .mynah-no-tabs-buttons-wrapper { + > .mynah-button { + padding: var(--mynah-sizing-2) var(--mynah-sizing-3); + max-height: initial; + max-width: initial; + height: auto; + width: auto; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_notification.scss b/vendor/mynah-ui/src/styles/components/_notification.scss new file mode 100644 index 00000000..9149f620 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_notification.scss @@ -0,0 +1,58 @@ +.mynah-notification { + display: inline-flex; + flex-flow: row nowrap; + align-items: flex-start; + justify-content: flex-start; + gap: var(--mynah-sizing-2); + padding: var(--mynah-sizing-2); + overflow: hidden; + position: relative; + &.mynah-notification-clickable { + pointer-events: all; + cursor: pointer; + } + > .mynah-ui-icon { + font-size: var(--mynah-font-size-large); + &- { + &ok-circled { + color: var(--mynah-color-status-success); + } + @each $status in info warning error { + &#{$status} { + color: var(--mynah-color-status-#{$status}); + } + } + } + } + > .mynah-notification-container { + flex: 1; + max-width: 50vw; + min-width: 200px; + display: inline-flex; + flex-flow: column nowrap; + align-items: flex-start; + justify-content: flex-start; + gap: var(--mynah-sizing-2); + overflow: hidden; + position: relative; + > .mynah-notification-title { + margin: 0; + &:empty { + display: none; + } + } + > .mynah-notification-content { + font-size: var(--mynah-font-size-small); + display: flex; + flex-flow: column nowrap; + justify-content: flex-start; + align-items: flex-start; + gap: var(--mynah-sizing-1); + > .mynah-button { + align-self: stretch; + background-color: var(--mynah-color-alternate); + color: var(--mynah-color-alternate-reverse); + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_overlay.scss b/vendor/mynah-ui/src/styles/components/_overlay.scss new file mode 100644 index 00000000..13e5c980 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_overlay.scss @@ -0,0 +1,187 @@ +.mynah-overlay { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: hidden; + box-sizing: border-box; + pointer-events: none; + z-index: var(--mynah-z-max); + &.mynah-overlay-dim-outside { + &:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--mynah-color-bg); + transition: var(--mynah-short-transition-rev-opacity); + opacity: 0.85; + pointer-events: all; + z-index: var(--mynah-sizing-10); + } + } + > .mynah-overlay-container { + --overlayLeftPos: calc(-1 * var(--mynah-sizing-1)); + --overlayTopPos: 0; + --overlayInnerHorizontalOrigin: left; + --overlayInnerVerticalOrigin: top; + position: absolute; + display: block; + overflow: hidden; + border-radius: var(--mynah-card-radius); + width: max-content; + height: max-content; + max-width: calc(100vw - var(--mynah-sizing-16)) !important; + max-height: calc(100vh - var(--mynah-sizing-16)) !important; + pointer-events: all; + z-index: var(--mynah-z-max); + + .mynah-card, + .mynah-card * { + pointer-events: none !important; + .mynah-syntax-highlighter-copy-buttons { + display: none; + & ~ *:last-child { + margin-bottom: 0 !important; + } + } + } + + &.background { + &:before { + content: ''; + background-color: transparent; + transition: var(--mynah-short-transition-rev); + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: auto; + height: auto; + pointer-events: none; + box-sizing: border-box; + opacity: 0; + border: var(--mynah-button-border-width) solid var(--mynah-color-border-default); + z-index: var(--mynah-z-5); + box-shadow: var(--mynah-shadow-overlay); + transform-origin: left bottom; + border-radius: inherit; + } + + &:after { + content: ''; + position: absolute; + width: var(--mynah-sizing-8); + height: var(--mynah-sizing-8); + left: 0; + top: 0; + opacity: 0; + border: var(--mynah-sizing-4); + background-color: var(--mynah-card-bg); + border-radius: inherit; + transition: var(--mynah-short-transition-rev); + transition-delay: 20ms; + } + } + + &.horizontal-direction { + &-to-left, + &-from-end-to-left { + --overlayLeftPos: -100%; + --overlayInnerHorizontalOrigin: right; + &:before { + transform-origin: right center; + } + &:after { + left: calc(100% - var(--mynah-sizing-4)); + } + } + &-at-center { + --overlayInnerHorizontalOrigin: center; + --overlayLeftPos: -50%; + &:after { + left: calc(50% - var(--mynah-sizing-4)); + } + } + } + &.vertical-direction { + &-to-top, + &-from-end-to-top { + --overlayTopPos: -100%; + --overlayInnerVerticalOrigin: bottom; + &:after { + top: calc(100% - var(--mynah-sizing-4)); + } + } + &-at-center { + --overlayInnerVerticalOrigin: center; + --overlayTopPos: -50%; + &:after { + top: calc(50% - var(--mynah-sizing-4)); + } + } + } + + > .mynah-overlay-inner-container { + display: inline-block; + overflow: hidden; + z-index: var(--mynah-z-1); + position: relative; + transform: translate3d(0, 0, 0) scale(0.95); + transform-origin: var(--overlayInnerHorizontalOrigin) var(--overlayInnerVerticalOrigin); + transition: var(--mynah-short-transition-rev); + width: 100%; + } + &:before { + transform: translate3d(0, 0, 0) scale(0.95); + transition: var(--mynah-short-transition-rev); + } + > .mynah-overlay-inner-container, + &:before { + opacity: 0; + transition-delay: 0ms; + } + + transform: translate3d(var(--overlayLeftPos), var(--overlayTopPos), 0); + } + &:not(.mynah-overlay-open) { + &, + & * { + pointer-events: none !important; + } + } + + &.mynah-overlay-open { + > .mynah-overlay-container { + > .mynah-overlay-inner-container { + transform: translate3d(0, 0, 0) scale(1); + } + &:before { + transform: translate3d(0, 0, 0) scale(1); + transition-delay: 50ms; + opacity: 1; + } + > .mynah-overlay-inner-container { + transition-delay: 20ms; + } + > .mynah-overlay-inner-container { + opacity: 1; + } + &:after { + transition-delay: 0ms; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 1; + } + } + &.mynah-overlay-close-on-outside-click { + pointer-events: all; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_progress.scss b/vendor/mynah-ui/src/styles/components/_progress.scss new file mode 100644 index 00000000..6a88b379 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_progress.scss @@ -0,0 +1,77 @@ +@import '../mixins'; +@import '../scss-variables'; +.mynah-progress-indicator { + position: relative; + border-radius: inherit; + width: 100%; + display: block; + opacity: 1; + transition: var(--mynah-bottom-panel-transition); + &.no-content, + &.no-content * { + opacity: 0; + pointer-events: none !important; + user-select: none !important; + } + > .mynah-progress-indicator-wrapper { + position: relative; + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + border-radius: inherit; + color: var(--mynah-color-default); + border-color: var(--mynah-color-default); + padding: var(--mynah-sizing-3); + padding-right: var(--mynah-sizing-1); + box-sizing: border-box; + gap: var(--mynah-sizing-2); + overflow: hidden; + @include gradient-border(); + + &[indeterminate='true'] { + animation: horizontal-roll 1750ms linear infinite both; + } + > * { + z-index: var(--mynah-z-2); + } + > .mynah-progress-indicator-value-bar { + position: absolute; + box-sizing: border-box; + top: 0; + left: 0; + bottom: 0; + background-clip: content-box, border-box; + background-origin: border-box; + height: auto; + background-color: var(--mynah-color-gradient-start); + z-index: var(--mynah-z-1); + opacity: 0.2; + transition: var(--mynah-bottom-panel-transition); + } + > .mynah-chat-item-buttons-container { + flex-shrink: 0; + } + > .mynah-progress-indicator-value-text { + flex: 1 0 0%; + } + > .mynah-progress-indicator-text { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + > .mynah-progress-indicator-text, + > .mynah-progress-indicator-value-text { + font-weight: bold; + } + @each $status in $mynah-statuses { + &[progress-status='#{$status}'] { + background-image: none; + border-color: var(--mynah-color-status-#{$status}); + > .mynah-progress-indicator-value-bar { + background-color: var(--mynah-color-status-#{$status}); + } + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_sheet.scss b/vendor/mynah-ui/src/styles/components/_sheet.scss new file mode 100644 index 00000000..970cd255 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_sheet.scss @@ -0,0 +1,209 @@ +#mynah-sheet-wrapper { + display: flex; + flex-flow: column nowrap; + position: absolute; + bottom: 0; + left: var(--mynah-sizing-2); + right: var(--mynah-sizing-2); + width: auto; + box-sizing: border-box; + z-index: var(--mynah-z-max); + opacity: 0; + transform: translate3d(0, 5vh, 0); + transition: var(--mynah-bottom-panel-transition); + overflow: visible; + + &:before { + transition: all 400ms cubic-bezier(0.25, 0, 0, 1); + content: ''; + position: absolute; + right: calc(-1 * var(--mynah-sizing-2)); + bottom: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: black; + opacity: 0; + pointer-events: none; + transform: translate3d(0, 0, 0) scale(2); + transform-origin: center center; + z-index: var(--mynah-z-sub); + } + & + #mynah-wrapper { + transition: var(--mynah-bottom-panel-transition); + } + &:not(.mynah-sheet-show) { + &, + & * { + pointer-events: none !important; + } + } + &.mynah-sheet-show { + opacity: 1; + transform: translate3d(0, 0, 0); + &:before { + opacity: 0.15; + } + & + #mynah-wrapper { + transform: translate3d(0, 0px, 0) scale(0.95); + transform-origin: top center; + opacity: 0.25; + &, + & * { + pointer-events: none !important; + } + } + } + + &:has(.mynah-sheet.mynah-sheet-fullscreen) { + left: 0 !important; + right: 0 !important; + height: 100%; + } +} + +.mynah-sheet { + pointer-events: all; + box-sizing: border-box; + transition: var(--mynah-short-transition-rev); + transform: translate3d(0, 0, 0); + display: flex; + flex-flow: column; + gap: var(--mynah-sizing-3); + align-items: stretch; + transform-origin: right bottom; + z-index: var(--mynah-z-1); + position: relative; + border: var(--mynah-button-border-width) solid var(--mynah-color-border-default); + border-top-right-radius: var(--mynah-card-radius); + border-top-left-radius: var(--mynah-card-radius); + border-bottom: none; + // TODO: Move to css-custom-properties + box-shadow: 0px -25px 20px -25px rgba(0, 0, 0, 0.15); + background-color: var(--mynah-color-bg); + padding: var(--mynah-sizing-3); + min-height: 20vh; + + &.mynah-sheet-fullscreen { + border: none; + border-radius: 0; + height: 100%; + overflow-x: hidden; + overflow-y: scroll; + } + + > .mynah-card-body { + flex: initial; + } + > .mynah-sheet-header { + display: flex; + box-sizing: border-box; + align-items: center; + > .mynah-sheet-back-button { + min-width: auto; + max-width: fit-content; + padding-left: 0; + &::after { + display: none; + } + } + > h4 { + flex: 1; + margin: 0; + } + } + > .mynah-sheet-description { + pointer-events: all; + &:empty { + display: none; + } + } + + > .mynah-sheet-body { + box-sizing: border-box; + display: flex; + flex-flow: column; + gap: var(--mynah-sizing-4); + align-items: stretch; + flex: 1; + overflow-y: scroll; + overflow-x: hidden; + > .mynah-sheet-header-status { + box-shadow: none; + > .mynah-ui-title-description-icon-wrapper { + row-gap: 0; + > .mynah-ui-title-description-icon-title > .mynah-sheet-header-status-title { + > *:first-child { + margin-top: 0; + } + > *:last-child { + margin-bottom: 0; + } + } + } + } + + > .mynah-detailed-list { + overflow-y: auto; + overflow-x: hidden; + } + + > .mynah-feedback-form-comment { + background-color: transparent; + padding: var(--mynah-sizing-3); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + resize: none; + width: 100%; + height: 100px; + max-height: 25vh; + border-radius: var(--mynah-input-radius); + font-size: var(--mynah-font-size-small); + outline: none; + color: var(--mynah-color-text-input); + background-color: var(--mynah-card-bg); + box-sizing: border-box; + font-family: inherit; + } + + > .mynah-feedback-form-select-wrapper { + position: relative; + display: flex; + box-sizing: border-box; + padding: var(--mynah-sizing-3); + justify-content: flex-end; + align-items: center; + > .mynah-feedback-form-select { + position: absolute; + width: 100%; + left: 0; + color: var(--mynah-color-text-default); + border-radius: var(--mynah-input-radius); + padding: var(--mynah-sizing-3); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + background-color: var(--mynah-card-bg); + -webkit-appearance: none; + appearance: none; + text-indent: 1px; + text-overflow: clip; + outline: none; + } + > .mynah-feedback-form-select-handle { + color: var(--mynah-color-text-weak); + pointer-events: none; + } + } + > .mynah-feedback-form-buttons-container { + display: flex; + justify-content: flex-end; + align-items: center; + gap: var(--mynah-sizing-1); + box-sizing: border-box; + } + > .mynah-feedback-form-description { + pointer-events: all; + &:empty { + display: none; + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_source-link-header.scss b/vendor/mynah-ui/src/styles/components/_source-link-header.scss new file mode 100644 index 00000000..6a529894 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_source-link-header.scss @@ -0,0 +1,188 @@ +> .mynah-source-link-header { + display: flex; + justify-content: space-between; + align-items: center; + opacity: 1; + gap: var(--mynah-sizing-2); + transition: var(--mynah-very-short-transition); + > .mynah-source-thumbnail { + font-style: normal; + font-weight: normal; + display: none; + width: var(--mynah-sizing-8); + height: var(--mynah-sizing-8); + font-variant: normal; + text-transform: none; + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + border-radius: 100%; + background-size: 85%; + background-clip: content-box; + background-position: center center; + background-repeat: no-repeat; + align-self: flex-start; + box-sizing: border-box; + background-color: var(--mynah-card-bg); + position: relative; + overflow: hidden; + &:after { + content: ''; + pointer-events: none; + box-sizing: border-box; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: auto; + height: auto; + border: 2px solid var(--mynah-card-bg); + border-radius: 100%; + } + } + > .mynah-source-link-title-wrapper { + flex: 1 1 0; + display: flex; + flex-flow: column nowrap; + align-items: flex-start; + gap: var(--mynah-sizing-half); + max-width: 100%; + overflow: hidden; + cursor: pointer; + &:hover > .mynah-source-link-title > .mynah-source-link-expand-icon { + opacity: 0.75; + align-self: baseline; + } + > .mynah-source-link-title { + font-size: var(--mynah-font-size-medium); + font-weight: 500; + color: var(--mynah-color-text-strong); + width: 100%; + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; + gap: var(--mynah-sizing-2); + white-space: normal; + text-overflow: ellipsis; + text-decoration: none; + outline: none; + overflow: hidden; + > .mynah-source-link-expand-icon { + transition: var(--mynah-short-transition-rev); + opacity: 0; + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-xsmall); + display: inline; + padding: var(--mynah-sizing-1); + } + } + > .mynah-source-link-url { + font-size: var(--mynah-font-size-xsmall); + font-weight: 300; + color: var(--mynah-color-text-link); + white-space: nowrap; + text-overflow: ellipsis; + text-decoration: none; + outline: none; + overflow: hidden; + max-width: 100%; + position: relative; + padding-bottom: var(--mynah-sizing-1); + display: inline-block; + transition: var(--mynah-short-transition-rev); + &:after { + content: ''; + position: relative; + display: block; + bottom: 0; + left: 0; + width: 100%; + height: 1px; + border-radius: var(--mynah-sizing-half); + background-color: currentColor; + transform: translate3d(-30%, 0, 0); + transition: var(--mynah-short-transition-rev); + opacity: 0; + } + &:hover { + &:after { + transform: translate3d(0, 0, 0); + opacity: 1; + } + } + > span { + &:not(:last-child) { + &:after { + content: '>'; + margin: 0 var(--mynah-sizing-1); + } + } + &:nth-child(3) ~ span:not(:last-child) { + display: none; + } + &:last-child { + font-weight: bold; + } + &:nth-child(3):not(:last-child):after { + content: '> ... >'; + } + } + } + > .mynah-title-meta-block { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: flex-start; + gap: var(--mynah-sizing-1); + margin-bottom: var(--mynah-sizing-half); + &:empty { + display: none; + } + > .mynah-title-meta-block-item { + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; + gap: var(--mynah-sizing-1); + padding: calc(var(--mynah-sizing-half) * 3); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + border-radius: var(--mynah-button-radius); + opacity: 0.75; + > .mynah-icon { + opacity: 0.5; + font-size: var(--mynah-font-size-medium); + color: var(--mynah-color-text-weak); + } + > .mynah-title-meta-block-item-text { + font-size: var(--mynah-font-size-xxsmall); + color: var(--mynah-color-text-weak); + &::first-letter { + text-transform: capitalize; + } + } + &.approved-answer { + border-color: green; + position: relative; + &:before { + content: ''; + border-radius: var(--mynah-button-radius); + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: green; + opacity: 0.04; + } + > .mynah-icon { + opacity: 1; + color: green; + border-radius: var(--mynah-button-radius); + width: var(--mynah-sizing-3); + height: var(--mynah-sizing-3); + } + } + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_spinner.scss b/vendor/mynah-ui/src/styles/components/_spinner.scss new file mode 100644 index 00000000..7772b1a8 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_spinner.scss @@ -0,0 +1,112 @@ +@import '../scss-variables'; +.mynah-ui-spinner-container { + display: block; + width: calc(var(--mynah-font-size-medium) + var(--mynah-font-size-xxlarge)); + aspect-ratio: 1 / 1; + position: relative; + overflow: visible; + --anim-speed: 2.5s; + --suppress: 5%; + + > span.mynah-ui-spinner-logo-part { + position: absolute; + width: 64%; + height: 64%; + overflow: visible; + left: 18%; + top: 18%; + &:not(.backdrop, .semi-backdrop) { + top: calc(3 * var(--suppress)); + left: calc(3 * var(--suppress)); + width: calc(100% - (6 * var(--suppress))); + height: calc(100% - (6 * var(--suppress))); + z-index: var(--mynah-z-3); + } + &.backdrop { + opacity: 0.25; + z-index: var(--mynah-z-1); + filter: blur(5px); + overflow: visible; + } + &.semi-backdrop { + top: calc(1 * var(--suppress)); + left: calc(1 * var(--suppress)); + width: calc(100% - (2 * var(--suppress))); + height: calc(100% - (2 * var(--suppress))); + z-index: var(--mynah-z-2); + > .mynah-ui-spinner-logo-mask { + overflow: visible; + &:after { + background-color: var(--mynah-color-bg); + content: ''; + position: absolute; + width: 100%; + height: 100%; + opacity: 0.5; + } + &:before { + opacity: 0.5; + filter: blur(5px); + } + } + } + + > .mynah-ui-spinner-logo-mask { + position: absolute; + -webkit-mask: center/100% no-repeat; + mask: center/100% no-repeat; + width: 100%; + height: 100%; + + &.base { + &::before { + animation: spinner-spin-delayed var(--anim-speed) 250ms ease-in-out infinite; + transform-origin: center center; + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--mynah-color-bg); + background-image: radial-gradient( + 190% 140% at 140% -15%, + var(--mynah-color-gradient-end), + var(--mynah-bg-gradient-mid), + var(--mynah-bg-gradient-mid), + var(--mynah-bg-gradient-end), + var(--mynah-bg-gradient-start) + ); + } + -webkit-mask-image: var(--mynah-ui-spinner-base); + mask-image: var(--mynah-ui-spinner-base); + } + + &.text { + opacity: 0.9; + background-color: white; + -webkit-mask-image: var(--mynah-ui-spinner-text); + mask-image: var(--mynah-ui-spinner-text); + } + } + + &.backdrop { + animation: opacity var(--anim-speed) 250ms linear infinite; + > .mynah-ui-spinner-logo-mask:before { + animation-name: spinner-spin-delayed; + + background-image: radial-gradient( + 100% 100% at 100% 0%, + var(--mynah-color-gradient-end), + var(--mynah-bg-gradient-mid), + var(--mynah-bg-gradient-end) + ); + } + } + &.semi-backdrop { + > .mynah-ui-spinner-logo-mask:before { + animation-name: spinner-spin-delayed-reverse; + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_syntax-highlighter.scss b/vendor/mynah-ui/src/styles/components/_syntax-highlighter.scss new file mode 100644 index 00000000..876149f8 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_syntax-highlighter.scss @@ -0,0 +1,391 @@ +@import '../scss-variables'; +@import '../mixins'; + +.mynah-syntax-highlighter { + display: flex; + flex-flow: column nowrap; + box-sizing: border-box; + overflow: hidden; + background-color: var(--mynah-card-bg); + max-width: 100%; + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + background-color: var(--mynah-color-syntax-bg); + position: relative; + border-radius: var(--mynah-input-radius); + line-height: var(--mynah-syntax-code-line-height); + color: var(--mynah-color-text-default); + + > .line-numbers-rows:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--mynah-color-syntax-bg); + opacity: 1; + pointer-events: none; + user-select: none; + } + > .mynah-syntax-highlighter-copy-buttons { + display: flex; + flex-flow: row-reverse nowrap; + align-items: center; + justify-content: flex-start; + padding: var(--mynah-sizing-half); + padding-left: var(--mynah-sizing-3); + order: 256000; + box-sizing: border-box; + margin: 0; + margin-block: 0 !important; + position: relative; + border-top: 1px solid var(--mynah-color-border-default); + z-index: var(--mynah-z-2); + &:empty { + display: none; + } + &:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--mynah-color-border-default); + opacity: 0.25; + } + > .mynah-syntax-highlighter-language { + flex: 1; + font-size: var(--mynah-font-size-xsmall); + display: inline-block; + opacity: 0.65; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .mynah-button { + min-width: var(--mynah-sizing-6); + height: auto; + min-height: auto; + * { + font-size: var(--mynah-font-size-xsmall); + color: var(--mynah-color-text-strong); + } + } + } + &.mynah-inline-code { + max-width: 100%; + margin: 0; + margin-block-start: 0; + margin-block-end: 0; + overflow: visible; + padding-top: 0; + padding-bottom: calc(2 * var(--mynah-border-width)); + padding-left: calc(3 * var(--mynah-border-width)); + padding-right: calc(3 * var(--mynah-border-width)); + &, + & > pre, + & > pre > code { + display: inline; + user-select: text; + position: relative; + font-size: inherit !important; + font-family: inherit !important; + white-space: normal; + word-wrap: break-word; + max-width: 100%; + overflow: hidden; + + & * { + white-space: normal; + } + } + & > pre, + & > pre > code { + padding: 0; + } + + &:after, + &:before { + display: none; + } + } + &:not(.mynah-inline-code) { + > pre { + max-height: var(--mynah-syntax-code-block-max-height); + } + &.wrap-code-block { + > pre, + > pre > code { + white-space: pre-wrap; + word-wrap: break-word; + } + } + } + > pre { + padding: var(--mynah-sizing-2); + position: relative; + margin: 0; + overflow-x: auto; + overflow-y: scroll; + box-sizing: border-box; + color: var(--mynah-color-syntax-code); + + *, + & { + font-size: var(--mynah-syntax-code-font-size) !important; + font-family: var(--mynah-syntax-code-font-family) !important; + } + + code { + color: var(--mynah-color-syntax-code); + filter: brightness(0.95); + white-space: pre; + background-color: inherit; + + @media (max-width: $mynah-code-blocks-wrap-under) { + white-space: pre-wrap; + } + } + + > code, + & { + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + tab-size: 4; + hyphens: none; + + @media (max-width: $mynah-code-blocks-wrap-under) { + white-space: pre-wrap; + word-wrap: break-word; + } + } + + .diff { + position: absolute; + top: 0; + left: 0; + padding: var(--mynah-sizing-2); + background-color: transparent; + color: transparent !important; + pointer-events: none; + user-select: none; + + .hljs-deletion { + background-color: var(--mynah-color-syntax-deletion); + } + + .hljs-addition { + background-color: var(--mynah-color-syntax-addition); + } + } + + .hljs { + color: var(--mynah-color-syntax-code); + + .hljs-comment, + .hljs-quote { + color: var(--mynah-color-syntax-comment); + font-style: italic; + } + + .hljs-doctag, + .hljs-keyword, + .hljs-formula { + color: var(--mynah-color-syntax-keyword); + } + + .hljs-section, + .hljs-name, + .hljs-selector-tag, + .hljs-subst, + .hljs-title.function_ { + color: var(--mynah-color-syntax-function); + } + + .hljs-literal, + .hljs-property { + color: var(--mynah-color-syntax-property); + } + + .hljs-string, + .hljs-regexp, + .hljs-attribute, + .hljs-meta .hljs-string { + color: var(--mynah-color-syntax-string); + } + + .hljs-attr, + .hljs-variable, + .hljs-template-variable, + .hljs-type, + .hljs-selector-class, + .hljs-selector-attr, + .hljs-selector-pseudo, + .hljs-number { + color: var(--mynah-color-syntax-variable); + } + + .hljs-symbol, + .hljs-bullet, + .hljs-link, + .hljs-meta, + .hljs-selector-id, + .hljs-title { + color: var(--mynah-color-syntax-operator); + } + + .hljs-built_in, + .hljs-title.class_, + .hljs-class .hljs-title { + color: var(--mynah-color-syntax-class-name); + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: bold; + } + + .hljs-link { + text-decoration: underline; + } + } + + > code::selection, + &::selection { + text-shadow: none; + background: #b3d4fc; + } + + &.line-numbers { + position: relative; + padding-left: 3.8em; + counter-reset: linenumber; + position: relative; + } + } + > .line-numbers-rows { + position: absolute; + background-color: inherit; + > span, + & { + pointer-events: none; + user-select: none; + } + top: var(--mynah-sizing-2); + font-size: 100%; + left: 0; + width: var(--mynah-sizing-12); + overflow: hidden; + text-overflow: clip; + letter-spacing: -1px; + border-right: 1px solid var(--mynah-color-border-default); + + > span { + display: block; + color: var(--mynah-color-border-default); + padding-right: 0.8em; + text-align: right; + } + } + + > .more-content-indicator { + position: relative; + display: none; + left: 0; + width: 100%; + padding: var(--mynah-sizing-half); + justify-content: center; + height: var(--mynah-sizing-10); + box-sizing: border-box; + margin-top: 0; + box-shadow: none; + background-color: var(--mynah-card-bg); + z-index: var(--mynah-z-1); + margin-top: calc(-1 * var(--mynah-sizing-10)); + &:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + box-sizing: border-box; + background-color: var(--mynah-color-syntax-bg); + opacity: 0.2; + } + > .mynah-button { + color: var(--mynah-color-text-weak); + } + } + + &.no-max:not(.mynah-inline-code) { + > pre { + max-height: initial; + overflow-y: hidden; + padding-bottom: var(--mynah-sizing-2); + -webkit-mask-image: none; + mask-image: none; + } + > .more-content-indicator { + position: relative; + top: initial; + transform: translateY(0); + } + } + + &.max-height-exceed { + &:not(.mynah-inline-code) > pre { + padding-bottom: var(--mynah-sizing-12); + } + &:not(.no-max, .mynah-inline-code) { + > pre { + padding-bottom: var(--mynah-sizing-13); + $listFader: linear-gradient( + to bottom, + black 0%, + black calc(1 * (var(--mynah-syntax-code-block-max-height) - (2 * var(--mynah-sizing-10)))), + transparent calc(1 * (var(--mynah-syntax-code-block-max-height) - (var(--mynah-sizing-10)))), + transparent 100% + ); + -webkit-mask-image: $listFader; + -webkit-mask-size: 100% 100%; + mask-image: $listFader; + mask-size: 100% 100%; + } + } + > .more-content-indicator { + display: flex; + } + } +} +li > .mynah-syntax-highlighter:not(.mynah-inline-code) { + margin-block-end: calc(var(--mynah-line-height) / 3); +} + +li > p + .mynah-syntax-highlighter:not(.mynah-inline-code) { + margin-block-start: calc(var(--mynah-line-height) / 3); +} + +.mynah-ui-syntax-highlighter-highlight-tooltip { + max-width: 80vw; + min-width: 10vw; + max-height: 80vh; + background-color: var(--mynah-card-bg); + border-radius: var(--mynah-card-radius); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + padding: var(--mynah-sizing-5); + + .mynah-card-body { + > p:first-child:last-of-type, + > p p:first-child { + margin: 0; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/_tab.scss b/vendor/mynah-ui/src/styles/components/_tab.scss new file mode 100644 index 00000000..33899bf5 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_tab.scss @@ -0,0 +1,116 @@ +.mynah-tabs-container { + flex-flow: row nowrap; + max-width: 100%; + overflow: hidden; + align-items: center; + position: relative; + background: var(--mynah-color-toggle); + color: var(--mynah-color-toggle-reverse); + justify-content: flex-start; + overflow-y: hidden; + overflow-x: auto; + scrollbar-color: transparent transparent; + scrollbar-width: none; + display: flex; + > span { + overflow: hidden; + height: 100%; + min-width: var(--mynah-sizing-8); + max-width: calc(3 * var(--mynah-sizing-15)); + flex-shrink: 0; + order: 10; + &.mynah-tab-item-pinned { + order: 5; + } + > .mynah-tab-item { + display: none; + &:not(:first-child) + .mynah-tab-item-label { + margin-left: calc(-1 * var(--mynah-sizing-1)); + } + &:not(:checked) + .mynah-tab-item-label { + &.indication:after { + content: ''; + position: absolute; + top: 50%; + transform: translateY(-50%); + left: calc(var(--mynah-sizing-1) + var(--mynah-sizing-half)); + height: var(--mynah-sizing-1); + width: var(--mynah-sizing-1); + background-color: var(--mynah-color-status-success); + border-radius: var(--mynah-sizing-1); + border: 1px solid var(--mynah-color-text-weak); + opacity: 0.75; + } + } + & + .mynah-tab-item-label { + cursor: pointer; + pointer-events: all; + position: relative; + z-index: var(--mynah-z-1); + padding: 0 var(--mynah-sizing-4) 0 var(--mynah-sizing-4); + gap: var(--mynah-sizing-1); + height: 100%; + box-sizing: border-box; + display: inline-flex; + overflow: hidden; + justify-content: stretch; + align-items: center; + color: var(--mynah-color-text-weak); + border-right: 1px solid var(--mynah-color-border-default); + border-top: 1px solid transparent; + background-color: var(--mynah-color-bg); + opacity: 0.75; + max-width: 100%; + &, + & * { + user-select: none; + } + > span { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 100%; + display: block; + &:empty { + display: none; + } + } + > .mynah-tabs-close-button { + margin-right: calc(-1 * var(--mynah-sizing-3)); + $closeBtnSize: var(--mynah-sizing-5); + min-width: $closeBtnSize; + max-width: $closeBtnSize; + min-height: $closeBtnSize; + max-height: $closeBtnSize; + padding: 0; + } + } + &:checked + .mynah-tab-item-label { + border-top-color: var(--mynah-color-button); + color: var(--mynah-color-text-default); + opacity: 1; + background-color: var(--mynah-color-tab-active); + } + &[disabled='disabled'] { + pointer-events: none !important; + & + .mynah-tab-item-label { + pointer-events: none !important; + opacity: 0.25; + } + } + } + } + + &.mynah-tabs-direction-vertical { + flex-flow: column nowrap; + gap: var(--mynah-sizing-3); + } +} + +.mynah-tabs-disabled-tooltip-container { + max-width: 30vw; + display: inline-block; + padding: var(--mynah-sizing-3); + font-size: 80%; + opacity: 0.85; +} diff --git a/vendor/mynah-ui/src/styles/components/_title-description-icon.scss b/vendor/mynah-ui/src/styles/components/_title-description-icon.scss new file mode 100644 index 00000000..0c96076d --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_title-description-icon.scss @@ -0,0 +1,33 @@ +.mynah-ui-title-description-icon-wrapper { + display: grid; + grid-template-columns: min-content 1fr; + grid-template-rows: min-content auto; + grid-template-areas: + 'icon title' + '. description'; + column-gap: var(--mynah-sizing-3); + row-gap: var(--mynah-sizing-1); + + position: relative; + box-sizing: border-box; + width: 100%; + overflow: hidden; + + > .mynah-ui-title-description-icon-icon { + grid-area: icon; + align-items: flex-start; + justify-content: center; + display: flex; + flex-flow: column nowrap; + } + > .mynah-ui-title-description-icon-title { + grid-area: title; + align-items: flex-start; + justify-content: center; + display: flex; + flex-flow: column nowrap; + } + > .mynah-ui-title-description-icon-description { + grid-area: description; + } +} diff --git a/vendor/mynah-ui/src/styles/components/_votes-wrapper.scss b/vendor/mynah-ui/src/styles/components/_votes-wrapper.scss new file mode 100644 index 00000000..7331fcd1 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/_votes-wrapper.scss @@ -0,0 +1,70 @@ +.mynah-card-votes-wrapper { + align-items: center; + gap: var(--mynah-sizing-2); + display: flex; + align-items: center; + justify-content: flex-end; + font-size: var(--mynah-font-size-small); + flex: 1; + > span.mynah-feedback-thanks { + overflow: hidden; + } + > .mynah-button > span { + padding: 0; + font-size: var(--mynah-font-size-small); + color: var(--mynah-color-text-link); + } + > .mynah-vote-text { + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-small); + } + > .mynah-card-vote { + position: relative; + overflow: visible; + display: inline-flex; + align-items: center; + box-sizing: border-box; + gap: var(--mynah-sizing-1); + > .mynah-vote-radio { + display: none; + } + > .mynah-vote-label { + z-index: var(--mynah-z-1); + cursor: pointer; + font-size: var(--mynah-font-size-small); + transition: var(--mynah-very-short-transition); + width: var(--mynah-sizing-5); + height: var(--mynah-sizing-5); + display: inline-flex; + justify-content: center; + align-items: center; + color: var(--mynah-color-text-weak); + opacity: 0.5; + padding: var(--mynah-sizing-1); + border: var(--mynah-border-width) solid transparent; + border-radius: var(--mynah-button-radius); + &:hover { + opacity: 1; + border-color: currentColor; + } + > * { + pointer-events: none; + } + > i { + height: calc(var(--mynah-sizing-6) + var(--mynah-sizing-half)); + } + } + > .mynah-vote-up-radio:checked { + & ~ .mynah-vote-up { + color: var(--mynah-color-text-default); + opacity: 1; + } + } + > .mynah-vote-down-radio:checked { + & ~ .mynah-vote-down { + color: var(--mynah-color-text-default); + opacity: 1; + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/card/_card-body.scss b/vendor/mynah-ui/src/styles/components/card/_card-body.scss new file mode 100644 index 00000000..cbc33353 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/card/_card-body.scss @@ -0,0 +1,214 @@ +@use 'sass:color'; + +.mynah-card-body { + flex-shrink: 0; + overflow: hidden; + position: relative; + display: block; + line-height: var(--mynah-line-height); + font-size: var(--mynah-font-size-medium); + + &:empty { + display: none; + } + + img { + max-width: 100%; + vertical-align: middle; + object-fit: cover; + } + + a { + pointer-events: bounding-box; + color: var(--mynah-color-text-link); + } + + span[start][end] { + display: none; + } + + > p:first-child { + &, + & h1:first-child, + & h2:first-child, + & h3:first-child, + & h4:first-child, + & h5:first-child, + & h6:first-child { + margin-top: 0; + } + } + + > p:first-child:last-of-type, + > p p:first-child { + margin: 0; + } + + &, + & > div { + h1 { + font-size: var(--mynah-font-size-xxlarge); + } + h2 { + font-size: var(--mynah-font-size-xlarge); + } + h3 { + font-size: var(--mynah-font-size-large); + } + + h1, + h2, + h3, + h4, + h5, + h6 { + line-height: initial; + @for $i from 1 through 6 { + & + h#{$i} { + margin-block-start: 0em; + } + } + } + + li { + > p:first-child { + margin-block-end: 0em; + } + } + + /* Add spacing only between top-level list items */ + > ol > li, + > ul > li, + > div > ol > li, + > div > ul > li { + margin-bottom: 0.4em; + } + + p + ol, + p + ul { + margin-block-start: 0; + } + + p, + h1, + h2, + h3, + h4, + h5, + h6 { + margin-block-start: 0.75em; + margin-block-end: 0.75em; + word-break: normal; + overflow-wrap: break-word; + padding-bottom: 1px !important; + + &:first-child { + margin-block-start: 0em; + } + &:last-child { + margin-block-end: 0em; + } + + user-select: text; + + &:empty { + display: none; + } + } + + // FULL WIDTH ITEMS + table, + hr, + video, + audio, + iframe, + object, + embed, + blockquote { + max-width: 100%; + width: 100%; + box-sizing: border-box; + border: none; + font-size: inherit; + } + + table { + table-layout: fixed; + } + + // BLOCKS + video { + background-color: var(--mynah-color-bg); + } + video, + audio, + iframe, + object, + embed, + img, + blockquote { + border-radius: var(--mynah-input-radius); + + &[aspect-ratio] { + max-width: 100%; + min-width: 100%; + width: 100%; + box-sizing: border-box; + } + &[aspect-ratio='16:9'] { + aspect-ratio: 16 / 9; + } + &[aspect-ratio='9:16'] { + aspect-ratio: 9 / 16; + } + &[aspect-ratio='21:9'] { + aspect-ratio: 21 / 9; + } + &[aspect-ratio='9:21'] { + aspect-ratio: 9 / 21; + } + &[aspect-ratio='4:3'] { + aspect-ratio: 4 / 3; + } + &[aspect-ratio='3:4'] { + aspect-ratio: 3 / 4; + } + &[aspect-ratio='3:2'] { + aspect-ratio: 3 / 2; + } + &[aspect-ratio='2:3'] { + aspect-ratio: 3 / 2; + } + &[aspect-ratio='1:1'] { + aspect-ratio: 1 / 1; + } + } + + hr { + display: block; + box-sizing: border-box; + height: var(--mynah-border-width); + margin-top: var(--mynah-sizing-2); + margin-bottom: var(--mynah-sizing-2); + background-color: var(--mynah-color-border-default); + border: none; + } + blockquote { + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + margin: 0; + padding: var(--mynah-sizing-2); + box-sizing: border-box; + } + + mark[reference-tracker] { + background-color: var(--mynah-color-highlight); + color: inherit; + cursor: help; + } + } + + ol, + ul { + padding-left: calc(var(--mynah-font-size-medium) * 2); + } +} diff --git a/vendor/mynah-ui/src/styles/components/card/_card.scss b/vendor/mynah-ui/src/styles/components/card/_card.scss new file mode 100644 index 00000000..f747897d --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/card/_card.scss @@ -0,0 +1,79 @@ +@import '../../scss-variables'; + +.mynah-card { + text-decoration: none; + outline: none; + position: relative; + transition: var(--mynah-short-transition-rev); + box-sizing: border-box; + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-2); + transform: translate3d(0, 0, 0); + flex: auto 0 0; + width: 100%; + overflow: hidden; + border-radius: var(--mynah-card-radius); + box-shadow: var(--mynah-shadow-card); + &:empty { + display: none; + } + + @for $i from 0 through 100 { + > .mynah-card-inner-order-#{$i} { + order: $i; + } + } + + .mynah-ui-clickable-item { + cursor: pointer; + } + + &.padding { + @each $size, $padding in $mynah-padding-sizes { + &-#{$size} { + padding: var(--mynah-sizing-#{$padding}); + @if $size == 'none' { + border-radius: 0; + padding: 0; + } + } + } + } + &.background { + background-color: var(--mynah-card-bg); + } + &:not(.background) { + box-shadow: none; + } + &.border { + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + } + + @each $status in $mynah-statuses { + &.status-#{$status} { + &:before { + content: ''; + z-index: 0; + background-color: var(--mynah-color-status-#{$status}); + opacity: 0.025; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + } + border-color: var(--mynah-color-status-#{$status}); + } + } + + > * { + z-index: var(--mynah-z-1); + position: relative; + } + + @import '../source-link-header'; + @import '../votes-wrapper'; +} + +@import 'card-body'; diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-item-card-information-card.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-item-card-information-card.scss new file mode 100644 index 00000000..dd61d72f --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-item-card-information-card.scss @@ -0,0 +1,97 @@ +@import '../../scss-variables'; +@import '../../mixins'; + +.mynah-chat-item-information-card { + > .mynah-chat-item-information-card-main { + display: flex; + flex-flow: column nowrap; + justify-content: space-between; + position: relative; + border-radius: var(--mynah-input-radius); + overflow: hidden; + box-sizing: border-box; + width: 100%; + height: fit-content; + + background-image: var(--mynah-color-gradient-main-half); + background-repeat: no-repeat; + background-size: 105% 105%; + background-position: center center; + border: var(--mynah-border-width) solid rgba(0, 0, 0, 0); + + > .mynah-ui-chat-item-inline-card { + background-color: var(--mynah-color-bg); + padding: var(--mynah-sizing-3) !important; + box-sizing: border-box; + margin: 0 !important; + transition: none !important; + border: none !important; + } + + > .mynah-chat-item-information-card-header-container { + padding: var(--mynah-sizing-3); + color: var(--mynah-color-gradient-alternate); + + > .mynah-ui-title-description-icon-icon { + font-size: var(--mynah-font-size-large); + } + > .mynah-ui-title-description-icon-title { + font-weight: bold; + } + } + } + + .mynah-chat-item-information-card-footer:empty { + display: none; + } + + &.has-footer { + padding-bottom: 0; + .mynah-chat-item-information-card-footer { + display: flex; + position: relative; + box-sizing: border-box; + width: 100%; + max-width: 100%; + flex-flow: row nowrap; + align-items: flex-start; + justify-content: flex-start; + overflow: hidden; + padding: var(--mynah-sizing-3); + gap: var(--mynah-sizing-3); + border: 1px solid currentColor; + border-radius: 0 0 var(--mynah-input-radius) var(--mynah-input-radius); + + &:empty { + display: none; + } + + > .mynah-ui-title-description-icon-description { + font-size: var(--mynah-font-size-small); + } + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: currentColor; + opacity: 10%; + pointer-events: none; + } + + @each $status in $mynah-statuses { + &.status-#{$status} { + color: var(--mynah-color-status-#{$status}); + } + } + } + > .mynah-chat-item-information-card-main { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + border-bottom: none !important; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-item-card-tabbed-card.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-item-card-tabbed-card.scss new file mode 100644 index 00000000..72854856 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-item-card-tabbed-card.scss @@ -0,0 +1,66 @@ +.mynah-tabbed-card-wrapper { + display: flex; + flex-flow: column nowrap; + justify-content: center; + align-items: center; + box-sizing: border-box; + width: 100%; + gap: var(--mynah-sizing-2); + + > .mynah-tabbed-card-contents { + width: 100%; + box-sizing: border-box; + padding: var(--mynah-sizing-4); + border-radius: var(--mynah-card-radius); + position: relative; + background-color: var(--mynah-color-syntax-bg); + + > .mynah-chat-item-card { + border: none; + border-radius: inherit; + margin: 0 !important; + padding: 0 !important; + border: none !important; + z-index: var(--mynah-z-1); + + > .mynah-card { + border-radius: var(--mynah-input-radius) !important; + padding: var(--mynah-sizing-3); + box-shadow: none; + border: none; + > div { + padding: 0; + } + } + .mynah-chat-item-information-card-main { + border: none; + border-radius: 0; + } + } + } + + > .mynah-tabbed-card-tabs { + > .mynah-tabs-container { + background-color: var(--mynah-color-syntax-bg); + border-radius: var(--mynah-button-radius); + padding: var(--mynah-sizing-1); + width: fit-content; + + // Default + .mynah-tab-item-label { + background-color: transparent; + padding: var(--mynah-sizing-2) var(--mynah-sizing-7) var(--mynah-sizing-2) var(--mynah-sizing-7); + gap: var(--mynah-sizing-2); + border-radius: var(--mynah-button-radius); + border: none; + color: var(--mynah-color-text-default); + } + + // Selected + .mynah-tab-item:checked + .mynah-tab-item-label { + background-color: var(--mynah-color-bg); + border: none; + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-item-card.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-item-card.scss new file mode 100644 index 00000000..47e3cb73 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-item-card.scss @@ -0,0 +1,669 @@ +@import '../../scss-variables'; +@import '../../mixins'; + +.mynah-chat-item-card { + display: inline-flex; + flex-flow: column nowrap; + position: relative; + box-sizing: border-box; + max-width: calc(100% - var(--mynah-chat-wrapper-spacing)); + min-width: 1%; + transition: var(--mynah-very-short-transition); + transition-property: transform; + transform: translate3d(0, min(30%, 10vh), 0) scale(0.99); + opacity: 0; + transform-origin: center bottom; + gap: var(--mynah-sizing-4); + z-index: var(--mynah-z-1); + + &-status { + @each $status in $mynah-statuses { + &-#{$status} { + > .mynah-card { + border-color: var(--mynah-color-status-#{$status}); + > .mynah-chat-item-card-icon { + color: var(--mynah-color-status-#{$status}); + } + } + } + } + } + + &.mynah-chat-item-card-content-horizontal-align-center { + text-align: center; + > .mynah-card { + align-self: center; + } + &.mynah-chat-item-directive.full-width { + > .mynah-card { + background-color: var(--mynah-color-bg); + // skips transparency layer related styling for Eclipse Webkit that causes rendering issues + body:not(.eclipse-swt-webkit) & { + backface-visibility: hidden; + } + max-width: calc(100% - var(--mynah-sizing-18)); + width: auto; + padding-left: var(--mynah-sizing-2); + padding-right: var(--mynah-sizing-2); + box-sizing: border-box; + z-index: var(--mynah-z-2); + } + &:before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 50%; + transform: translateY(-50%); + background-color: var(--mynah-color-border-default); + height: var(--mynah-border-width); + } + } + } + + &.text-shimmer { + p, + span { + @include text-shimmer; + } + } + + &.full-width { + max-width: 100%; + min-width: 100%; + + > .mynah-chat-item-buttons-container { + justify-content: flex-start; + } + > .mynah-card { + > .mynah-chat-item-card-header { + @include full-width-header(); + } + } + } + + &.muted { + *:not(.mynah-chat-item-card-header-status, .mynah-chat-item-card-header-status *) { + color: var(--mynah-color-text-disabled) !important; + } + + .status-default { + color: var(--mynah-color-text-disabled) !important; + } + + .language-diff { + display: none !important; + } + + body:not(.eclipse-swt-webkit) & { + *:not(.mynah-chat-item-card-header, .mynah-card) { + filter: grayscale(100%) !important; + -webkit-filter: grayscale(100%) !important; + opacity: 90%; + } + + .mynah-chat-item-card-header-status, + .mynah-chat-item-card-header-status * { + filter: none !important; + -webkit-filter: none !important; + opacity: 60%; + } + } + + .mynah-button { + cursor: default; + &:active, + &:focus-visible, + &:hover { + &:after { + transform: translate3d(-7%, 0, 0); + opacity: 0; + } + } + } + } + + &.no-padding { + > .mynah-card { + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + overflow: hidden; + gap: 0; + border-radius: var(--mynah-card-radius) !important; + > .mynah-chat-item-tree-view-wrapper { + padding: 0; + > .mynah-chat-item-tree-view-wrapper-container { + margin-left: calc(-1 * var(--mynah-sizing-2)); + margin-right: calc(-1 * var(--mynah-sizing-2)); + min-width: calc(100% + var(--mynah-sizing-4)); + border: none; + padding-left: var(--mynah-sizing-2); + padding-right: var(--mynah-sizing-2); + } + } + > .mynah-card-body { + padding: 0 var(--mynah-sizing-2) var(--mynah-sizing-2) var(--mynah-sizing-2); + > .mynah-syntax-highlighter { + margin-left: calc(-1 * var(--mynah-sizing-2)); + margin-right: calc(-1 * var(--mynah-sizing-2)); + min-width: calc(100% + var(--mynah-sizing-4)); + border-left: none; + border-right: none; + border-bottom: none; + border-radius: 0; + &:not(:first-child) { + border-top: none; + } + &:last-child { + margin-bottom: calc(-1 * var(--mynah-sizing-2)); + } + > .mynah-syntax-highlighter-copy-buttons, + > pre { + padding-left: var(--mynah-sizing-2); + padding-right: var(--mynah-sizing-2); + } + } + } + + > .mynah-chat-item-card-header { + min-height: var(--mynah-sizing-9); + padding: var(--mynah-sizing-1) var(--mynah-sizing-1) var(--mynah-sizing-1) var(--mynah-sizing-2); + @include full-width-header(); + } + } + } + + --mynah-chat-item-more-content-indicator-bg-color: var(--mynah-color-bg); + + &.mynah-chat-item-auto-collapse { + > .more-content-indicator { + order: 1000; + justify-content: center; + background-color: var(--mynah-chat-item-more-content-indicator-bg-color); + box-shadow: 0 -10px 10px -5px var(--mynah-chat-item-more-content-indicator-bg-color); + height: var(--mynah-sizing-10); + z-index: var(--mynah-z-2); + position: absolute; + bottom: 0; + left: var(--mynah-border-width); + right: var(--mynah-border-width); + padding: var(--mynah-sizing-1) var(--mynah-sizing-2); + border-bottom-left-radius: var(--mynah-card-radius-corner); + border-bottom-right-radius: var(--mynah-card-radius); + > button > i { + body:not(.eclipse-swt-webkit) & { + opacity: 0.5; + } + } + } + &.mynah-chat-item-collapsed { + > .mynah-card { + max-height: 25vh; + overflow-y: auto; + } + } + > .mynah-card:after { + content: ''; + position: relative; + display: block; + order: 10000; + width: 100%; + min-height: var(--mynah-sizing-7); + height: var(--mynah-sizing-7); + } + } + &:not(.mynah-chat-item-auto-collapse) { + > .more-content-indicator { + display: none; + } + } + + &.mynah-chat-item-card-has-icon { + > .mynah-card { + &.padding-none { + > .mynah-chat-item-card-icon { + left: 0; + top: 0; + } + } + padding-left: var(--mynah-sizing-8); + > .mynah-chat-item-card-icon { + position: absolute; + left: var(--mynah-sizing-3); + top: calc(var(--mynah-sizing-1) + var(--mynah-line-height)); + + &[class*='icon-status-']:not(.icon-status-none) { + padding: var(--mynah-sizing-2); + border-radius: var(--mynah-input-radius); + body:not(.eclipse-swt-webkit) & { + mask-size: 100% 60%; + } + } + @each $status in $mynah-statuses { + &.icon-status-#{$status} { + background-color: var(--mynah-color-status-#{$status}); + } + } + &.icon-status-main { + background-image: var(--mynah-color-gradient-main-half); + } + &.icon-status-primary { + background-color: var(--mynah-color-button); + } + } + } + } + + &.mynah-chat-item-hover-effect { + &:last-child { + margin-bottom: var(--mynah-sizing-8); + } + &:hover { + cursor: pointer; + z-index: var(--mynah-z-2); + > .mynah-card { + box-shadow: var(--mynah-shadow-card-hover); + } + } + } + + &.reveal { + opacity: 1; + transform: translate3d(0, 0, 0) scale(1, 1); + } + + .mynah-chat-item-card { + max-width: 100%; + width: 100%; + min-width: auto; + padding: 0; + .mynah-chat-item-card-related-content > .mynah-chat-item-card-related-content-title { + font-size: var(--mynah-font-size-large); + } + } + + > span.invisible { + display: none; + } + + &.mynah-chat-item-code-result { + min-width: 70%; + } + > .mynah-chat-item-card-icon-wrapper { + background-color: var(--mynah-card-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + color: var(--mynah-color-text-default); + padding: var(--mynah-sizing-2); + border-radius: 100%; + } + > .mynah-card { + flex-shrink: 0; + > .mynah-chat-items-spinner { + overflow: visible; + z-index: var(--mynah-z-3); + gap: var(--mynah-sizing-2); + justify-content: flex-start; + align-items: center; + display: flex; + flex-flow: row nowrap; + @include text-shimmer; + } + > .mynah-card-body { + padding: var(--mynah-border-width); + & > div { + > .mynah-syntax-highlighter { + body:not(.eclipse-swt-webkit) & { + filter: contrast(1.15) brightness(0.85); + } + } + + @for $i from 1 through 4 { + h#{$i} { + &:first-child { + margin-top: 0; + } + } + } + } + > .mynah-chat-item-card-related-content { + margin-top: var(--mynah-sizing-3); + padding-top: var(--mynah-sizing-4); + > .mynah-chat-item-card-related-content-title { + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-xsmall); + margin: 0; + } + .mynah-source-link-title { + font-size: var(--mynah-font-size-small); + font-weight: 400; + } + } + } + + > .mynah-chat-item-card-footer { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + &:not(:empty) { + margin-top: calc(-1 * var(--mynah-sizing-1)); + } + &:empty { + display: none; + } + > .mynah-ui-chat-item-small-card { + padding: 0; + margin: 0; + border: none; + flex: 0; + > .mynah-card.padding-none { + padding-right: calc(1px + var(--mynah-border-width)); + } + } + } + + > .mynah-chat-item-card-title { + display: flex; + flex-flow: row nowrap; + align-items: center; + > .mynah-chat-item-card-title-text { + flex: 1; + color: var(--mynah-color-text-weak); + } + > .mynah-button { + flex-shrink: 0; + margin-left: auto; + } + & + .mynah-chat-item-card-header { + @include full-width-header(); + } + } + + > .mynah-chat-item-card-header { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + > .mynah-ui-chat-item-small-card { + padding: 0; + margin: 0; + border: none; + flex: 0; + > .mynah-card.padding-none { + padding-right: calc(1px + var(--mynah-border-width)); + h1, + h2, + h3, + h4, + h5, + h6 { + &:last-child { + margin: 0; + } + } + } + } + + > .mynah-chat-item-card-header-status { + flex-shrink: 0; + display: inline-flex; + flex-flow: row nowrap; + gap: var(--mynah-sizing-1); + justify-content: flex-start; + align-items: center; + font-size: var(--mynah-font-size-large); + padding-right: var(--mynah-sizing-1); + min-height: var(--mynah-sizing-7); + @each $status in $mynah-statuses { + &.status-#{$status} { + color: var(--mynah-color-status-#{$status}); + } + } + > .mynah-chat-item-card-header-status-text { + font-size: var(--mynah-font-size-small); + } + } + } + + > .mynah-chat-item-card-summary { + display: flex; + flex-flow: column nowrap; + > .mynah-chat-item-card-summary-content { + display: flex; + flex-flow: row nowrap; + align-items: flex-start; + // Adding background color and border for chat summary header + background-color: var(--mynah-card-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + border-radius: var(--mynah-card-radius); + > .mynah-chat-item-summary-button { + margin-right: calc(-1 * var(--mynah-sizing-1)); + align-self: flex-start; + margin-top: calc(var(--mynah-sizing-1) * 1.5); + > i { + vertical-align: top; + } + } + > .mynah-ui-chat-item-small-card { + flex: 1; + > .mynah-card { + > .mynah-chat-item-card-header { + align-items: flex-start; + } + } + } + } + > .mynah-chat-item-card-summary-collapsed-content { + display: none; + flex-flow: column nowrap; + } + &.show-summary > .mynah-chat-item-card-summary-collapsed-content { + display: flex; + } + } + } + + > .mynah-chat-item-followup-question { + display: inline-flex; + flex-flow: column nowrap; + position: relative; + gap: var(--mynah-sizing-2); + > .mynah-chat-item-followup-question-text { + color: var(--mynah-color-text-weak); + font-size: var(--mynah-font-size-xsmall); + font-style: italic; + } + > .mynah-chat-item-followup-question-options-wrapper { + display: inline-flex; + flex-flow: row wrap; + max-width: 100%; + gap: var(--mynah-sizing-2); + > .mynah-chat-item-followup-question-option { + color: var(--mynah-color-text-default); + body:not(.eclipse-swt-webkit) &:not(:hover) { + opacity: 0.75; + } + } + } + } + + .mynah-chat-item-card-related-content { + display: flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-2); + overflow: hidden; + position: relative; + > .mynah-chat-item-card-related-content-item { + max-width: 100%; + > .mynah-card { + padding: 0; + border-radius: 0; + box-shadow: none; + } + } + &:not(.expanded) { + > .mynah-chat-item-card-related-content-item { + $maxItems: 1; + $selector: '&'; + @for $i from 1 through $maxItems { + $selector: #{$selector} + ':not(:nth-of-type(' + #{$i} + '))'; + } + #{$selector} { + display: none; + } + &:nth-of-type(#{$maxItems}) { + margin-bottom: calc(-1 * var(--mynah-sizing-4)); + pointer-events: none; + @include list-fader-bottom(var(--mynah-sizing-12)); + body:not(.eclipse-swt-webkit) & { + mask-image: linear-gradient(to top, transparent var(--mynah-sizing-1), black 70%); + -webkit-mask-image: linear-gradient(to top, transparent var(--mynah-sizing-1), black 70%); + } + } + &:nth-of-type(#{$maxItems + 1}) { + & ~ .mynah-chat-item-card-related-content-show-more { + display: flex; + } + } + } + } + + > .mynah-chat-item-card-related-content-show-more { + display: none; + max-width: 80%; + z-index: var(--mynah-z-3); + margin-block-start: 0; + margin-block-end: 0; + align-self: center; + height: var(--mynah-sizing-8); + filter: none; + > span, + > i { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: var(--mynah-font-size-xsmall); + } + } + } + &.mynah-chat-item-directive { + max-width: 100%; + min-width: 100%; + color: var(--mynah-color-text-weak); + + > .mynah-card > .mynah-card-body { + font-size: var(--mynah-font-size-xsmall); + color: var(--mynah-color-text-weak); + } + &.mynah-chat-item-auto-collapse > .more-content-indicator { + --mynah-chat-item-more-content-indicator-bg-color: var(--mynah-color-bg); + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + &.no-padding > .mynah-card > .mynah-card-body { + padding: 0; + } + } + + &.mynah-chat-item-system-prompt, + &.mynah-chat-item-prompt { + align-self: flex-end; + > .mynah-chat-item-card-icon-wrapper { + align-self: flex-end; + margin-bottom: calc(-2 * var(--mynah-sizing-1)); + background-color: var(--mynah-card-bg-alternate); + color: var(--mynah-color-text-alternate); + } + .mynah-chat-item-followup-question, + .mynah-chat-item-card-related-content { + align-items: flex-end; + } + + .mynah-syntax-highlighter { + > .mynah-syntax-highlighter-copy-buttons { + display: none; + } + } + + .mynah-chat-item-card-related-content > .mynah-card, + > .mynah-card { + padding: var(--mynah-sizing-2) var(--mynah-sizing-3); + background-color: var(--mynah-card-bg-alternate); + border-bottom-right-radius: var(--mynah-card-radius-corner); + border: none; + &, + > .mynah-card-body { + color: var(--mynah-color-text-alternate); + overflow-wrap: break-word; + + a { + color: var(--mynah-color-text-link-alternate); + } + } + } + .mynah-chat-item-card-related-content > .mynah-card { + border: none; + } + + &.mynah-chat-item-auto-collapse > .more-content-indicator { + --mynah-chat-item-more-content-indicator-bg-color: var(--mynah-card-bg-alternate); + color: var(--mynah-color-text-alternate); + border-bottom-right-radius: var(--mynah-card-radius-corner); + border-bottom-left-radius: var(--mynah-card-radius); + } + } + &.mynah-chat-item-system-prompt { + &.mynah-chat-item-auto-collapse > .more-content-indicator { + --mynah-chat-item-more-content-indicator-bg-color: var(--mynah-color-status-warning); + } + > .mynah-card { + background-color: var(--mynah-color-status-warning); + } + } + + &.mynah-chat-item-empty > .mynah-card:empty { + display: none; + } + &.mynah-chat-item-answer-stream.mynah-chat-item-empty.stream-ended { + display: none; + } + &.mynah-chat-item-answer, + &.mynah-chat-item-code-result, + &.mynah-chat-item-answer-stream { + overflow: visible; + > .mynah-card { + border-bottom-left-radius: var(--mynah-card-radius-corner); + } + } + &.mynah-chat-item-ai-prompt, + &.mynah-chat-item-answer, + &.mynah-chat-item-answer-stream { + > .mynah-chat-item-card-icon-wrapper { + align-self: flex-start; + margin-bottom: calc(-2 * var(--mynah-sizing-1)); + background-color: var(--mynah-card-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + color: var(--mynah-color-text-default); + } + > .mynah-chat-item-card-related-content { + > .mynah-chat-item-card-related-content-item > .mynah-card-compact { + opacity: 1; + > .mynah-source-link-header { + opacity: 1; + } + } + } + } + + &.mynah-ui-chat-item-inline-card { + &.mynah-chat-item-card-has-icon { + > .mynah-card { + padding-left: var(--mynah-sizing-3); + > .mynah-chat-item-card-icon { + left: 0; + } + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-item-tree-view.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-item-tree-view.scss new file mode 100644 index 00000000..9e25c6b5 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-item-tree-view.scss @@ -0,0 +1,413 @@ +.mynah-chat-item-tree-view-wrapper { + margin: 0; + margin-block-end: 0 !important; + margin-block-start: 0 !important; + padding: var(--mynah-sizing-half); + min-width: 100%; + box-sizing: border-box; + + &.mynah-chat-item-tree-view-flat-list { + .mynah-chat-tree-view-folder-open, + .mynah-chat-tree-view-folder-closed { + > .mynah-chat-item-tree-view-button { + min-height: auto; + height: auto; + box-sizing: border-box; + line-height: initial; + } + .mynah-chat-item-tree-view-button:not(.mynah-chat-item-tree-view-root) { + display: none; + } + } + + .mynah-chat-item-folder-child:not(.mynah-chat-item-tree-view-button.mynah-chat-item-tree-view-root ~ *) { + padding-left: 0 !important; + } + } + + > .mynah-chat-item-tree-view-wrapper-container { + // background-color: var(--mynah-card-bg); + color: var(--mynah-color-text-default); + margin-block-start: 0 !important; + margin-block-end: 0 !important; + position: relative; + + > .mynah-chat-item-tree-view-wrapper-title { + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + position: relative; + padding: var(--mynah-sizing-2) 0; + gap: var(--mynah-sizing-2); + + > h4 { + margin: 0; + } + } + + > .mynah-chat-item-tree-view { + padding: 0; + overflow-y: scroll; + } + + .mynah-chat-item-tree-view { + box-sizing: border-box; + display: flex; + flex-flow: column nowrap; + justify-content: flex-start; + align-items: flex-start; + + > .mynah-chat-item-tree-view-button { + gap: var(--mynah-sizing-1); + & ~ .mynah-chat-item-folder-child { + padding-left: var(--mynah-sizing-5); + } + } + + .mynah-chat-item-tree-view-button-title { + display: inline-flex; + gap: var(--mynah-sizing-1); + align-items: center; + } + + .mynah-chat-item-tree-view-button-weak-title { + color: var(--mynah-color-text-weak); + } + + .mynah-chat-item-folder-child { + box-sizing: border-box; + } + } + + .mynah-chat-item-tree-view-file-item { + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + gap: var(--mynah-sizing-1); + overflow-y: hidden; + cursor: pointer; + position: relative; + box-sizing: content-box; + min-height: auto; + height: auto; + margin: 0; + &.mynah-chat-item-tree-view-not-clickable { + cursor: default; + &:active, + &:focus-visible, + &:hover { + filter: brightness(0.95); + &:after { + transform: translate3d(-7%, 0, 0); + opacity: 0; + } + } + } + + > .mynah-chat-item-tree-view-file-item-actions { + z-index: var(--mynah-z-3); + } + &:not(:hover) { + > .mynah-chat-item-tree-view-file-item-actions { + opacity: 0; + pointer-events: none; + } + } + + &-status { + &-info { + > .mynah-chat-item-tree-view-file-item-title, + > .mynah-chat-item-tree-view-file-item-details > .mynah-ui-icon { + color: var(--mynah-color-status-info); + } + } + + &-success { + > .mynah-chat-item-tree-view-file-item-title, + > .mynah-chat-item-tree-view-file-item-details > .mynah-ui-icon { + color: var(--mynah-color-status-success); + } + } + + &-warning { + > .mynah-chat-item-tree-view-file-item-title, + > .mynah-chat-item-tree-view-file-item-details > .mynah-ui-icon { + color: var(--mynah-color-status-warning); + } + } + + &-error { + > .mynah-chat-item-tree-view-file-item-title, + > .mynah-chat-item-tree-view-file-item-details > .mynah-ui-icon { + color: var(--mynah-color-status-error); + } + } + } + + &-details { + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + gap: var(--mynah-sizing-1); + z-index: var(--mynah-z-1); + flex: 1; + flex-shrink: 2; + &:empty { + display: none; + } + + > .mynah-chat-item-tree-view-file-item-details-changes { + white-space: nowrap; + flex-shrink: 0; + display: inline-flex; + flex-flow: row nowrap; + gap: var(--mynah-sizing-1); + justify-content: center; + align-items: center; + > .changes-added { + color: var(--mynah-color-status-success); + } + > .changes-deleted { + color: var(--mynah-color-status-error); + } + > .changes-total { + color: var(--mynah-color-status-info); + } + } + > .mynah-chat-item-tree-view-file-item-details-text { + flex: 1; + white-space: nowrap; + color: var(--mynah-color-text-weak); + } + } + + &-actions { + display: flex; + flex-flow: row-reverse nowrap; + justify-content: flex-end; + align-items: center; + font-size: 90%; + z-index: var(--mynah-z-1); + padding-right: var(--mynah-sizing-1); + flex-shrink: 0; + > .mynah-button { + width: auto; + height: auto; + min-width: auto; + min-height: auto; + padding: var(--mynah-sizing-half); + border-radius: var(--mynah-button-radius); + margin: 0; + + &.info { + color: var(--mynah-color-status-info); + } + + &.success { + color: var(--mynah-color-status-success); + } + + &.warning { + color: var(--mynah-color-status-warning); + } + + &.error { + color: var(--mynah-color-status-error); + } + } + } + + &-deleted { + text-decoration: line-through; + } + + &-title { + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + gap: var(--mynah-sizing-1); + z-index: var(--mynah-z-1); + flex-shrink: 0.01; + + > .mynah-ui-icon { + flex-shrink: 0; + } + + > span { + opacity: 0.75; + white-space: nowrap; + } + } + + > .mynah-chat-single-file-icon { + position: relative; + padding: var(--mynah-sizing-2); + display: flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + border-radius: var(--mynah-input-radius); + color: var(--mynah-card-bg); + &:before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: var(--mynah-color-text-default); + opacity: 0.5; + pointer-events: none; + border-radius: inherit; + } + } + } + + > .mynah-chat-item-tree-view-file-item { + padding: var(--mynah-sizing-1); + box-sizing: border-box; + width: 100%; + border-radius: inherit; + } + } + + .mynah-chat-item-tree-view-button, + .mynah-chat-item-tree-view-file-item { + padding-left: 0; + padding-right: 0; + &:hover { + &:after { + opacity: 0; + } + > .mynah-chat-item-tree-view-file-item-title > .mynah-chat-item-tree-view-file-item-title-text { + opacity: 1; + } + } + } +} + +.mynah-chat-item-tree-view-wrapper-label { + border-top-left-radius: var(--mynah-card-radius); + border-top-right-radius: var(--mynah-card-radius); + background-color: var(--mynah-color-alternate); + margin-bottom: 0px; + padding: var(--mynah-sizing-4); +} + +.mynah-chat-item-tree-view-wrapper-feedback-label { + padding-left: var(--mynah-sizing-4); +} + +.mynah-chat-item-tree-view-wrapper-feedback-div { + float: right; + + button:first-child { + margin-right: var(--mynah-sizing-2); + } +} + +.mynah-chat-item-tree-view-license { + &:before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background-color: var(--mynah-card-bg-alternate); + opacity: 0.07; + pointer-events: none; + } + + color: var(--mynah-color-text-weak); + + ul { + margin: 0; + padding-left: var(--mynah-sizing-8); + + > li { + &:not(:first-child) { + margin-top: var(--mynah-sizing-2); + } + + .mynah-card-body p { + margin: 0; + } + } + } + + .mynah-chat-item-tree-view-license-dropdown-button > span { + max-width: unset; + } +} + +.mynah-chat-item-tree-file-pill { + display: inline-flex; + align-items: center; + background-color: var(--mynah-color-syntax-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + border-radius: var(--mynah-input-radius); + padding: 0 calc(4 * var(--mynah-border-width)); + font-size: var(--mynah-font-size-small) !important; + color: var(--mynah-color-syntax-code); + cursor: pointer; + white-space: nowrap; + transition: background-color 0.2s ease; + margin-left: var(--mynah-sizing-1); + + &:hover { + background-color: var(--mynah-color-button); + } + + &.mynah-chat-item-tree-file-pill-deleted { + text-decoration: line-through; + opacity: 0.6; + } +} + +.mynah-chat-item-card-header { + .mynah-chat-item-tree-file-pill { + margin-left: var(--mynah-sizing-1); + vertical-align: middle; + } + + .mynah-card-body { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0; + row-gap: var(--mynah-sizing-1); + + p { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0; + row-gap: var(--mynah-sizing-1); + margin: 0; + } + } +} + +.mynah-chat-item-card-icon-inline { + margin-right: var(--mynah-sizing-1); + vertical-align: middle; + flex-shrink: 0; +} + +// TODO: Update this depending on the general file list structure +.mynah-chat-item-answer, +.mynah-chat-item-answer-answer-stream { + > .mynah-card.padding-none:not(.background, .border) .mynah-chat-item-tree-view-wrapper { + &, + > .mynah-chat-item-tree-view-wrapper-container { + padding: 0; + border-radius: var(--mynah-button-radius); + background-color: transparent; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-items-container.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-items-container.scss new file mode 100644 index 00000000..ab820a1e --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-items-container.scss @@ -0,0 +1,55 @@ +@import '../../_mixins.scss'; + +> .mynah-chat-items-container { + position: relative; + display: flex; + flex-grow: 1; + flex-shrink: 1; + overflow-x: hidden; + overflow-y: auto; + flex-flow: column nowrap; + align-items: flex-start; + padding: var(--mynah-chat-wrapper-spacing); + padding-bottom: 0; + gap: var(--mynah-chat-wrapper-spacing); + scroll-behavior: smooth; + + @include list-fader-bottom(); + + &.set-scroll { + scroll-snap-type: y mandatory; + } + + > .mynah-chat-items-conversation-container { + gap: var(--mynah-chat-wrapper-spacing); + position: relative; + display: flex; + flex-shrink: 0; + overflow: hidden; + flex-flow: column nowrap; + align-items: flex-start; + width: 100%; + box-sizing: border-box; + &:empty { + display: none; + } + + > .intersection-observer { + position: absolute; + bottom: var(--mynah-sizing-8); + left: 0; + right: 0; + width: auto; + height: var(--mynah-sizing-1); + background-color: transparent; + z-index: var(--mynah-z-4); + pointer-events: none; + } + + &:last-child { + scroll-snap-align: start; + padding-bottom: calc(var(--mynah-chat-wrapper-spacing) + var(--mynah-sizing-1)); + min-height: 100%; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-attachment.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-attachment.scss new file mode 100644 index 00000000..6c1f6d24 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-attachment.scss @@ -0,0 +1,80 @@ +@import '../../mixins'; +.outer-container { + display: flex; + &:not(:empty) { + margin-top: var(--mynah-sizing-3); + } +} + +.mynah-prompt-attachment-container.vertical-overflow { + & pre { + // Show fading effect at the bottom when overflowing + @include list-fader-bottom(var(--mynah-sizing-16)); + } +} + +.mynah-prompt-attachment-container { + box-sizing: border-box; + max-width: 100%; + max-height: calc(2 * var(--mynah-sizing-12)); + > .mynah-button { + position: absolute; + background-color: var(--mynah-card-bg) !important; + width: var(--mynah-sizing-7) !important; + min-width: var(--mynah-sizing-7) !important; + max-width: var(--mynah-sizing-7) !important; + height: var(--mynah-sizing-7) !important; + border-radius: 100%; + right: calc(-1 * var(--mynah-sizing-2)); + top: calc(-1 * var(--mynah-sizing-2)); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + opacity: 1 !important; + filter: brightness(0.95); + } + &:not(:hover) { + > .mynah-button { + display: none; + } + } + > .mynah-card-body > .mynah-syntax-highlighter { + & pre { + text-overflow: ellipsis; + } + } + .more-content-indicator { + display: none !important; + } +} + +.mynah-prompt-input-snippet-attachment-overlay { + max-width: 90vw; + max-height: 70vh; + > .mynah-card-body { + > .mynah-syntax-highlighter { + max-height: calc(70vh - var(--mynah-sizing-12)) !important; + } + } +} +.mynah-prompt-attachment-container, +.mynah-prompt-input-snippet-attachment-overlay { + position: relative; + overflow: visible; + width: 100%; + box-sizing: border-box; + > .mynah-card-body { + max-height: 100%; + > .mynah-syntax-highlighter { + user-select: none; + pointer-events: none; + margin: 0; + max-height: 100%; + overflow: hidden; + > .mynah-syntax-highlighter-copy-buttons { + display: none; + } + } + } + & pre { + text-overflow: ellipsis; + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-context-tooltip.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-context-tooltip.scss new file mode 100644 index 00000000..02248f46 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-context-tooltip.scss @@ -0,0 +1,50 @@ +.mynah-overlay > .mynah-overlay-container > .mynah-overlay-inner-container > .mynah-chat-prompt-context-tooltip { + display: flex; + position: relative; + box-sizing: border-box; + width: 100%; + flex-flow: row nowrap; + align-items: flex-start; + justify-content: flex-start; + overflow: hidden; + padding: var(--mynah-sizing-2) var(--mynah-sizing-3); + color: var(--mynah-color-text-default); + border-radius: var(--mynah-input-radius); + transition: var(--mynah-short-transition-rev); + gap: var(--mynah-sizing-3); + + > .mynah-ui-icon { + margin-top: var(--mynah-sizing-half); + font-size: var(--mynah-font-size-large); + color: var(--mynah-color-text-default); + } + + > .mynah-chat-prompt-context-tooltip-container { + flex: 1; + display: flex; + position: relative; + box-sizing: border-box; + flex-flow: column nowrap; + align-items: flex-start; + justify-content: flex-start; + overflow: hidden; + gap: var(--mynah-sizing-1); + + > .mynah-chat-prompt-context-tooltip-name { + font-family: var(--mynah-font-family); + font-weight: bold; + flex: 0 1 0%; + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + > .mynah-chat-prompt-context-tooltip-description { + color: var(--mynah-color-text-weak); + flex: 1 0 100%; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-top-bar-context.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-top-bar-context.scss new file mode 100644 index 00000000..8dac4dec --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-top-bar-context.scss @@ -0,0 +1,74 @@ +@import '../../mixins'; + +> span.pinned-context-pill { + position: relative; + border-radius: calc(var(--mynah-input-radius) / 2); + display: inline-flex; + white-space: nowrap; + overflow-wrap: break-word; + z-index: var(--mynah-z-1); + cursor: pointer; + padding: 0.2em 0.3em 0.05em 0.3em; + margin-bottom: var(--mynah-border-width); + flex-flow: row nowrap; + justify-content: center; + align-items: center; + gap: var(--mynah-sizing-half); + font-size: var(--mynah-font-size-small); + > .mynah-ui-icon { + font-size: var(--mynah-font-size-xxsmall); + flex-shrink: 0; + } + + // Hide hover icons by default + > .mynah-ui-icon.hover-icon { + display: none; + } + + // Hover state + &:hover { + // Show hover icon + > .mynah-ui-icon.hover-icon { + display: inline-flex; + } + + // Hide normal icon and @-char + > .mynah-ui-icon:not(.hover-icon) { + display: none; + } + } + + > .label { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100px; + } + + &:before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: auto; + height: calc(100%); + border-width: var(--mynah-button-border-width); + border-style: solid; + border-radius: inherit; + border-color: var(--mynah-color-text-disabled, var(--mynah-color-border-default)); + z-index: var(--mynah-z-sub); + } + + &.overflow-button { + &:hover:before { + background-color: var(--mynah-color-text-weak); + transition: var(--mynah-very-short-transition); + transform: translate3d(0%, 0, 0); + opacity: 0.15; + border-width: var(--mynah-button-border-width); + border-style: solid; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-top-bar.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-top-bar.scss new file mode 100644 index 00000000..870a58e8 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-top-bar.scss @@ -0,0 +1,44 @@ +@import '../../mixins'; + +> .mynah-prompt-input-top-bar { + display: flex; + flex-wrap: nowrap; + overflow: visible; + box-sizing: border-box; + gap: var(--mynah-sizing-2); + align-items: flex-start; + position: relative; + width: 100%; + + &.hidden { + display: none; + } + + > span.title { + color: var(--mynah-color-text-weak); + cursor: pointer; + } + + > span.placeholder { + color: var(--mynah-color-text-input-placeholder); + text-align: center; + } + + > .mynah-button { + min-height: 0; + min-width: var(--mynah-sizing-5); + padding-inline: var(--mynah-sizing-half); + font-size: var(--mynah-font-size-small) !important; + } + + > span.top-bar-button { + margin-left: auto; + + > .mynah-button { + min-height: 0; + font-size: var(--mynah-font-size-small) !important; + } + } + + @import 'chat-prompt-top-bar-context'; +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-wrapper.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-wrapper.scss new file mode 100644 index 00000000..79efe8be --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-prompt-wrapper.scss @@ -0,0 +1,483 @@ +@import '../../mixins'; +> .mynah-chat-prompt-wrapper { + display: block; + padding: calc(var(--mynah-chat-wrapper-spacing) / 2) var(--mynah-chat-wrapper-spacing) + var(--mynah-chat-wrapper-spacing) var(--mynah-chat-wrapper-spacing); + &.hidden { + display: none; + &, + & * { + pointer-events: none; + } + } + + &.awaits-confirmation .mynah-chat-prompt-input { + caret-color: transparent !important; + } + + > .mynah-chat-prompt-input-label { + transition: var(--mynah-short-transition); + padding: var(--mynah-sizing-2) 0; + box-sizing: border-box; + max-width: 100%; + overflow: hidden; + &:empty { + padding: 0; + } + } + > .mynah-chat-prompt { + display: flex; + flex-flow: column nowrap; + overflow: hidden; + box-sizing: border-box; + border: inset var(--mynah-border-width) solid transparent; + background-color: var(--mynah-input-bg); + border-radius: var(--mynah-input-radius); + padding: var(--mynah-sizing-2); + position: relative; + + > .mynah-chat-prompt-input-wrapper { + display: flex; + flex-flow: column nowrap; + overflow: visible; + box-sizing: border-box; + gap: var(--mynah-sizing-2); + align-items: flex-start; + position: relative; + + @import 'chat-prompt-top-bar'; + + > .mynah-chat-prompt-input-inner-wrapper { + display: flex; + flex: 1; + gap: var(--mynah-sizing-2); + position: relative; + align-self: center; + overflow: hidden; + flex-flow: row nowrap; + width: 100%; + + > .mynah-chat-prompt-input-command-wrapper { + align-self: flex-start; + box-sizing: border-box; + flex-shrink: 0; + line-height: calc(var(--mynah-line-height) / 100 * 80); + display: flex; + flex-flow: row nowrap; + align-items: center; + flex-shrink: 0; + + &.hidden { + display: none; + } + + > .mynah-chat-prompt-input-command-text { + user-select: none; + cursor: pointer; + font-family: var(--mynah-font-family); + font-size: var(--mynah-font-size-medium); + color: var(--mynah-color-text-strong); + white-space: nowrap; + max-width: calc(10 * var(--mynah-font-size-medium)); + text-overflow: ellipsis; + text-shadow: 0 0 1px var(--mynah-color-text-strong); + } + } + + > .mynah-chat-prompt-input { + font-family: var(--mynah-font-family); + border: none; + resize: none; + background-color: rgba(0, 0, 0, 0); + font-size: var(--mynah-font-size-medium); + color: var(--mynah-color-text-input); + caret-color: var(--mynah-color-text-input); + outline: none; + width: 100%; + max-height: 20vh; + line-height: calc(var(--mynah-line-height) / 100 * 80); + white-space: pre-wrap; + word-break: normal; + overflow-wrap: break-word; + padding: 0; + overflow-x: hidden; + display: block; + box-sizing: border-box; + min-height: calc(var(--mynah-line-height) / 100 * 160); + transition: var(--mynah-short-transition-rev); + + &[disabled] { + pointer-events: none; + } + + &.empty { + text-overflow: ellipsis; + &::before { + content: attr(placeholder); + pointer-events: none; + text-overflow: ellipsis; + position: absolute; + z-index: var(--mynah-z-0); + font-weight: inherit; + font-size: inherit; + color: var(--mynah-color-text-input-placeholder); + max-width: 100%; + overflow: hidden; + overflow-wrap: break-word; + box-sizing: border-box; + padding-bottom: var(--mynah-line-height); + } + } + + > span.cursor, + > span.eol { + max-width: 0; + line-height: inherit; + max-height: var(--mynah-line-height); + min-height: var(--mynah-line-height); + display: inline; + margin: 0; + padding: 0; + } + > span.context { + position: relative; + border-radius: calc(var(--mynah-input-radius) / 2); + display: inline; + white-space: nowrap; + overflow-wrap: break-word; + z-index: var(--mynah-z-1); + cursor: pointer; + padding: 0.2em 0.3em 0.05em 0.3em; + margin-bottom: var(--mynah-border-width); + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + gap: var(--mynah-sizing-half); + font-size: var(--mynah-font-size-small); + > .mynah-ui-icon { + font-size: var(--mynah-font-size-xxsmall); + } + + > .at-char { + display: none; + } + + // Hide hover icons by default + > .mynah-ui-icon.hover-icon { + display: none; + } + + // Hover state + &:not(.no-hover):hover { + // Show hover icon + > .mynah-ui-icon.hover-icon { + display: inline-flex; + } + + // Hide normal icon and @-char + > .mynah-ui-icon:not(.hover-icon) { + display: none; + } + } + &:before { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + width: auto; + height: calc(100%); + border-radius: inherit; + border-width: var(--mynah-button-border-width); + border-color: var(--mynah-color-text-disabled, var(--mynah-color-border-default)); + border-style: solid; + z-index: var(--mynah-z-sub); + } + } + } + + & ~ .mynah-chat-prompt-button-wrapper { + display: flex; + flex-flow: row nowrap; + overflow: visible; + box-sizing: border-box; + gap: var(--mynah-sizing-half); + flex-flow: row nowrap; + align-items: center; + justify-content: flex-end; + overflow: visible; + width: 100%; + color: inherit; + > .mynah-button { + &.mynah-chat-prompt-stop-button, + &.mynah-chat-prompt-button { + flex-shrink: 0; + } + + &.mynah-chat-prompt-stop-button { + &:not(.hidden) { + & + .mynah-chat-prompt-button { + display: none; + } + } + &.hidden { + display: none; + } + } + } + + > .mynah-prompt-input-options { + flex: 1; + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; + gap: var(--mynah-sizing-1); + &:empty { + display: none; + } + > .mynah-chat-item-form-items-container { + flex-flow: row nowrap; + align-items: center; + gap: var(--mynah-sizing-2); + padding: 0; + > .mynah-form-input-wrapper { + > .mynah-form-input-container:not(.no-border) { + padding: var(--mynah-sizing-1) var(--mynah-sizing-2); + width: auto; + min-width: auto; + } + > .mynah-form-input-container.no-border { + > .select-auto-width-sizer { + font-size: var(--mynah-font-size-xsmall); + padding-right: calc(var(--mynah-sizing-half) + 1em); + } + > select { + width: auto; + min-width: auto; + color: var(--mynah-color-text-default); + font-size: var(--mynah-font-size-xsmall); + & + i { + font-size: var(--mynah-font-size-xsmall); + } + } + } + } + } + } + } + + &.no-text ~ .mynah-chat-prompt-button-wrapper > .mynah-chat-prompt-button:not([disabled]), + & ~ .mynah-chat-prompt-button-wrapper > .mynah-chat-prompt-button[disabled] { + pointer-events: none; + background-color: transparent; + i { + color: var(--mynah-color-text-disabled); + } + } + } + } + + > .mynah-chat-prompt-attachment-wrapper { + &:empty { + display: none; + } + + > .mynah-chat-attachment-item { + position: relative; + display: inline-block; + max-width: 250px; + cursor: pointer; + + &:after { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + border-radius: var(--mynah-card-radius); + opacity: 0; + transition: var(--mynah-short-transition-rev); + z-index: var(--mynah-z-2); + background-color: var(--mynah-color-alternate); + } + + > .mynah-chat-attachment-delete-icon { + color: var(--mynah-color-alternate-reverse); + opacity: 0; + transition: var(--mynah-short-transition-rev); + position: absolute; + left: 50%; + top: 50%; + width: 30px; + height: 30px; + margin-left: -15px; + margin-top: -15px; + z-index: var(--mynah-z-3); + + > i { + width: 30px; + height: 30px; + } + } + + &:hover { + &:after { + opacity: 0.75; + } + + > .mynah-chat-attachment-delete-icon { + opacity: 1; + } + } + + > .mynah-card { + pointer-events: none !important; + + * { + pointer-events: none !important; + } + + > .mynah-card-body { + display: none; + } + } + } + } + + > .mynah-prompt-input-progress-field { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + box-sizing: border-box; + z-index: var(--mynah-z-1); + display: flex; + min-width: 100%; + overflow: hidden; + justify-content: stretch; + align-items: stretch; + background-color: var(--mynah-card-bg); + > .mynah-progress-indicator-wrapper { + width: 100%; + } + &:not(.no-content) ~ .mynah-chat-prompt-input-wrapper { + > .mynah-chat-prompt-input-inner-wrapper > .mynah-chat-prompt-input { + min-height: var(--mynah-line-height); + max-height: var(--mynah-line-height); + } + > .mynah-chat-prompt-button-wrapper { + display: none; + } + > .mynah-prompt-input-top-bar { + display: none; + } + } + } + + &::before { + content: ''; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + max-height: 100%; + box-sizing: content-box; + border-radius: inherit; + box-sizing: border-box; + transition: var(--mynah-short-transition-rev); + pointer-events: none; + } + &::before { + border: var(--mynah-border-width) solid var(--mynah-color-text-input-border-focused); + } + } + + &.input-has-focus > .mynah-chat-prompt { + &::before { + border: var(--mynah-border-width) solid var(--mynah-color-text-input-border-focused); + } + } + + &:not(.hidden) + .mynah-chat-wrapper-footer-spacer + .mynah-chat-prompt-input-info { + padding-top: 0; + } + & + .mynah-chat-wrapper-footer-spacer + .mynah-chat-prompt-input-info { + margin-top: calc(-1 * var(--mynah-sizing-2)); + flex-basis: fit-content; + flex-shrink: 0; + flex-grow: 0; + } +} + +> .mynah-chat-prompt-input-sticky-card { + &:not(:empty) { + padding: var(--mynah-chat-wrapper-spacing); + background-color: var(--mynah-card-bg); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + border-radius: var(--mynah-input-radius); + box-sizing: border-box; + margin: var(--mynah-chat-wrapper-spacing); + box-shadow: var(--mynah-shadow-card-border); + width: calc(100% - 2 * var(--mynah-chat-wrapper-spacing)); + } + + > .mynah-chat-item-card { + max-width: 100%; + width: 100%; + > .mynah-card { + border-bottom-left-radius: 0 !important; + border: none; + box-shadow: none; + } + } +} + +> .mynah-chat-prompt-input-info { + display: flex; + flex-flow: row nowrap; + justify-content: center; + box-sizing: border-box; + overflow: hidden; + padding: var(--mynah-chat-wrapper-spacing); + text-align: center; + flex-shrink: 0; + color: var(--mynah-color-text-weak); + + &, + & * { + font-size: var(--mynah-font-size-xxsmall) !important; + line-height: 1rem; + } + + &:empty { + display: none; + } + + > * { + margin: 0; + margin-block-start: 0; + margin-block-end: 0; + margin-top: 0; + margin-bottom: 0; + max-width: 100%; + box-sizing: border-box; + } +} + +@media only screen and (max-height: 450px) { + > .mynah-chat-prompt-wrapper + > .mynah-chat-prompt + > .mynah-chat-prompt-input-wrapper + > .mynah-chat-prompt-input-inner-wrapper + > .mynah-chat-prompt-input { + min-height: var(--mynah-line-height); + &.empty::before { + white-space: nowrap; + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-wrapper-dropdown.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-wrapper-dropdown.scss new file mode 100644 index 00000000..d35234c9 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-wrapper-dropdown.scss @@ -0,0 +1,10 @@ +.mynah-dropdown-list-container { + padding: var(--mynah-sizing-1) var(--mynah-sizing-1) var(--mynah-sizing-1) var(--mynah-sizing-2); + display: flex; + justify-content: flex-end; + width: 100%; + + .mynah-dropdown-list-wrapper { + margin-bottom: 0; + } +} diff --git a/vendor/mynah-ui/src/styles/components/chat/_chat-wrapper.scss b/vendor/mynah-ui/src/styles/components/chat/_chat-wrapper.scss new file mode 100644 index 00000000..68199e47 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/chat/_chat-wrapper.scss @@ -0,0 +1,290 @@ +@import '../../mixins'; +@import '../../scss-variables'; + +.mynah-chat-prompt-overlay-buttons-container { + display: inline-flex; + flex-flow: column nowrap; + gap: var(--mynah-sizing-2); + justify-content: flex-start; + align-items: flex-start; +} +.mynah-chat-wrapper { + position: relative; + width: inherit; + max-width: inherit; + height: 100%; + flex: 1 0 100%; + flex-flow: column nowrap; + overflow: hidden; + justify-content: center; + align-items: stretch; + display: flex; + position: absolute; + left: -100%; + opacity: 0; + visibility: hidden; + + box-sizing: border-box; + > div[class^='mynah-chat'] { + width: inherit; + max-width: inherit; + box-sizing: border-box; + } + &:after { + transition: var(--mynah-very-short-transition); + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: auto; + height: auto; + background-color: black; + z-index: var(--mynah-z-max); + opacity: 0; + pointer-events: none; + transform: translate3d(0, 0, 0) scale(2); + transform-origin: center center; + } + + &.tab-mode-switch-animation { + &, + > .mynah-ui-tab-header-details, + > .mynah-chat-wrapper-footer-spacer, + > .mynah-chat-wrapper-header-spacer, + > .mynah-chat-items-container, + > .mynah-chat-items-container > .mynah-chat-items-conversation-container > .mynah-chat-item-card { + transition: var(--mynah-main-wrapper-transition); + } + + &:not(.show-tab-header-details) { + &, + > .mynah-chat-wrapper-footer-spacerinfo, + > .mynah-chat-items-container { + transition-delay: 100ms; + } + } + } + + &.compact-mode { + padding: 0 var(--mynah-sizing-10); + > .mynah-chat-wrapper-footer-spacer { + flex-grow: 0.8 !important; + } + > .mynah-chat-wrapper-header-spacer { + flex-grow: 0.2 !important; + } + > .mynah-chat-items-container { + flex-grow: 0; + } + } + + &.show-tab-header-details { + > .mynah-ui-tab-header-details { + opacity: 1; + max-height: 100vh; + } + &:not(.compact-mode) { + > .mynah-chat-wrapper-footer-spacer { + flex-grow: 1 !important; + } + > .mynah-chat-wrapper-header-spacer { + flex-grow: 0 !important; + } + } + > .mynah-chat-items-container { + flex-grow: 0; + padding-top: var(--mynah-sizing-1); + padding-bottom: var(--mynah-sizing-12); + } + > .mynah-chat-items-container > .mynah-chat-items-conversation-container { + > .mynah-chat-item-card { + &:not(.mynah-chat-item-prompt, .mynah-chat-item-system-prompt) { + max-width: 100%; + min-width: 100%; + } + > .mynah-card > .mynah-chat-item-card-footer { + padding-top: 0; + border-top: none; + > .mynah-ui-chat-item-small-card { + flex: 1; + } + } + > .mynah-card > .mynah-chat-item-card-header { + padding-bottom: 0; + border-bottom: none; + > .mynah-ui-chat-item-small-card { + flex: 1; + } + } + } + } + } + + > .mynah-ui-tab-header-details { + padding: var(--mynah-sizing-4); + transition-delay: 150ms; + flex-shrink: 0; + overflow: hidden; + max-height: 0; + opacity: 0; + > .mynah-ui-title-description-icon-icon > .mynah-ui-icon { + font-size: var(--mynah-font-size-xxlarge); + } + > .mynah-ui-title-description-icon-title { + font-weight: bold; + } + > .mynah-ui-title-description-icon-title, + > .mynah-ui-title-description-icon-description { + font-size: var(--mynah-font-size-xxlarge); + } + &:empty { + display: none; + } + } + + &:not(.with-background) { + > .mynah-ui-gradient-background { + opacity: 0; + } + } + > .more-content-indicator { + > .mynah-button { + pointer-events: all; + &:after { + transform: translate3d(0%, 0, 0); + opacity: 0.1; + } + &:hover:after { + opacity: 0.15; + } + } + } + &:not(.more-content) > .more-content-indicator { + opacity: 0; + > .mynah-button { + pointer-events: none; + } + } + + .mynah-drag-overlay-content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: calc(var(--mynah-z-max) + 2); + display: flex; + flex-direction: row; + align-items: center; + gap: var(--mynah-sizing-3); + color: var(--mynah-color-text-default); + font-size: var(--mynah-font-size-large); + text-align: center; + pointer-events: none; + + > .mynah-ui-icon { + font-size: var(--mynah-font-size-xxlarge); + opacity: 0.8; + } + } + + .mynah-drag-blur-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--mynah-drag-overlay-blur-bg); + backdrop-filter: blur(3px); + z-index: calc(var(--mynah-z-max) + 1); + pointer-events: none; + } + + &.drag-over { + .mynah-drag-overlay-content { + z-index: calc(var(--mynah-z-max) + 2); + } + } + + @import 'chat-items-container'; + @import 'chat-prompt-wrapper'; + &.loading { + &:before { + display: block; + animation: horizontal-roll 1250ms linear infinite both; + } + > .mynah-chat-items-container { + padding-bottom: var(--mynah-sizing-14); + > .mynah-chat-items-conversation-container:last-child { + > .mynah-chat-item-card.mynah-chat-item-answer-stream { + &:last-child { + position: relative; + > .mynah-card { + min-width: 100px; + > .mynah-chat-item-card-footer { + display: none; + } + } + &.mynah-chat-item-empty { + .mynah-chat-items-spinner { + display: inline-flex; + } + } + } + } + } + } + } +} + +.mynah-overlay > .mynah-overlay-container > .mynah-overlay-inner-container { + &:has(> .mynah-chat-prompt-chars-indicator) { + padding: var(--mynah-sizing-4); + } +} + +.mynah-overlay + > .mynah-overlay-container + > .mynah-overlay-inner-container + > .mynah-chat-prompt-quick-picks-overlay-wrapper { + max-height: 70vh; + height: auto; + display: flex; + flex-direction: column; + overflow: hidden; + + // Quick pick selector info header styling + > .mynah-chat-prompt-quick-picks-header { + padding: var(--mynah-sizing-3); + > .mynah-ui-title-description-icon-title { + font-weight: bold; + color: var(--mynah-color-text-strong); + } + > .mynah-ui-title-description-icon-description { + color: var(--mynah-color-text-weak); + } + @each $status in $mynah-statuses { + &.status-#{$status} { + > .mynah-ui-title-description-icon-icon { + color: var(--mynah-color-status-#{$status}); + } + } + } + } + + > .mynah-detailed-list { + padding: var(--mynah-sizing-2); + flex: 1; + } +} + +.mynah-chat-items-container, +.mynah-chat-prompt-input-sticky-card { + @import 'chat-item-card'; +} + +@import 'chat-item-tree-view'; +@import 'chat-prompt-attachment'; +@import 'chat-prompt-context-tooltip'; +@import 'chat-wrapper-dropdown'; diff --git a/vendor/mynah-ui/src/styles/components/form-items/_form-item-list.scss b/vendor/mynah-ui/src/styles/components/form-items/_form-item-list.scss new file mode 100644 index 00000000..7d8c0fa9 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/form-items/_form-item-list.scss @@ -0,0 +1,58 @@ +.mynah-form-item-list-wrapper { + display: flex; + flex-direction: column; + width: 100%; + gap: var(--mynah-sizing-2); + + > .mynah-form-item-list-rows-wrapper { + display: flex; + flex-direction: column; + justify-content: center; + gap: var(--mynah-sizing-2); + + > .mynah-form-item-list-row { + display: flex; + align-items: center; + gap: var(--mynah-sizing-2); + > .mynah-form-item-list-row-header { + display: flex; + flex-flow: column nowrap; + flex: 1; + gap: var(--mynah-sizing-half); + } + > .mynah-form-item-list-row-remove-all-button { + opacity: 0 !important; + pointer-events: none; + &:first-child { + display: none !important; + } + } + + > .mynah-form-item-list-row-items-container { + width: 100%; + display: flex; + gap: var(--mynah-sizing-2); + + > .mynah-chat-item-form-items-container { + width: 100%; + flex-flow: row nowrap; + justify-content: stretch; + gap: var(--mynah-sizing-2); + padding-bottom: 0; + + > .mynah-form-input-wrapper { + flex: 1; + } + + .mynah-form-input-label { + color: var(--mynah-color-text-weak); + } + } + } + } + } + + > .mynah-form-item-list-add-button { + width: fit-content; + } +} diff --git a/vendor/mynah-ui/src/styles/components/form-items/_form-item-pill-box.scss b/vendor/mynah-ui/src/styles/components/form-items/_form-item-pill-box.scss new file mode 100644 index 00000000..0c16fdfb --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/form-items/_form-item-pill-box.scss @@ -0,0 +1,98 @@ +/*! + * Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +.mynah-form-item-pill-box-wrapper { + position: relative; + display: flex; + flex-direction: column; + padding: var(--mynah-sizing-2); + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + background-color: var(--mynah-card-bg); + border-radius: var(--mynah-input-radius); + min-height: calc(var(--mynah-sizing-6) + var(--mynah-sizing-half)); + box-sizing: border-box; + transform: translateZ(0px); + + &:focus-within { + box-shadow: 0 0 0 2px var(--mynah-color-border-focus-shadow); + } +} + +.mynah-form-item-pill-box-pills-container { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 8px; +} + +.mynah-form-item-pill-box-input { + border: none; + outline: none; + background: transparent; + width: 100%; + resize: none; + font-family: var(--mynah-font-family); + font-size: var(--mynah-font-size-medium); + color: var(--mynah-color-text-input); + + &::placeholder { + color: var(--mynah-color-text-input-placeholder); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } +} + +.mynah-form-item-pill { + display: inline-flex; + align-items: center; + background-color: var(--mynah-color-primary-light); + border-radius: 16px; + padding: 4px 8px; + max-width: 100%; + + .mynah-form-item-pill-text { + margin-right: 4px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + } + + .mynah-form-item-pill-remove { + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + opacity: 0.7; + + &:hover { + opacity: 1; + } + } +} + +[disabled] .mynah-form-item-pill-box-wrapper, +.mynah-form-item-pill-box-wrapper[disabled] { + &::after { + content: ''; + position: absolute; + top: calc(var(--mynah-border-width) * -1); + left: calc(var(--mynah-border-width) * -1); + right: calc(var(--mynah-border-width) * -1); + bottom: calc(var(--mynah-border-width) * -1); + background-color: var(--mynah-color-text-disabled); + border-radius: var(--mynah-input-radius); + z-index: var(--mynah-z-sub); + opacity: 10%; + } + + .mynah-form-item-pill-box-input { + pointer-events: none; + opacity: 50%; + } +} diff --git a/vendor/mynah-ui/src/styles/components/form-items/_radio-group.scss b/vendor/mynah-ui/src/styles/components/form-items/_radio-group.scss new file mode 100644 index 00000000..a58d7fd6 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/form-items/_radio-group.scss @@ -0,0 +1,84 @@ +.mynah-form-input-radio-wrapper { + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + &:not(:last-child) { + margin-right: var(--mynah-sizing-4); + } + + > .mynah-form-input-radio-label { + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + position: relative; + gap: var(--mynah-sizing-2); + cursor: pointer; + + box-sizing: border-box; + min-width: var(--mynah-sizing-6); + min-height: var(--mynah-sizing-6); + + > input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; + &:checked + .mynah-form-input-radio-check { + border-color: var(--mynah-color-button); + background-color: var(--mynah-color-button); + > .mynah-ui-icon { + color: var(--mynah-color-button-reverse); + opacity: 1; + } + } + &[type='checkbox'].as-checkbox { + & + .mynah-form-input-radio-check { + border-radius: 25%; + } + } + } + > .mynah-form-input-radio-check { + position: relative; + transition: var(--mynah-bottom-panel-transition); + width: var(--mynah-sizing-4); + height: var(--mynah-sizing-4); + border-radius: 100%; + overflow: hidden; + box-sizing: border-box; + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + + > .mynah-ui-icon { + position: relative; + transition: inherit; + opacity: 0; + font-size: 85%; + z-index: var(--mynah-z-2); + } + + & + span:empty { + display: none; + } + } + } +} + +/* Vertical radio group styles */ +.mynah-form-input-vertical { + display: flex; + flex-direction: column; +} + +.mynah-form-input-radio-wrapper-vertical { + display: block; + + &:not(:last-child) { + margin-bottom: var(--mynah-sizing-2); + } +} diff --git a/vendor/mynah-ui/src/styles/components/form-items/_switch.scss b/vendor/mynah-ui/src/styles/components/form-items/_switch.scss new file mode 100644 index 00000000..5972aec6 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/form-items/_switch.scss @@ -0,0 +1,87 @@ +.mynah-form-input-switch-wrapper { + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + gap: var(--mynah-sizing-1); + &:not(:last-child) { + margin-right: var(--mynah-sizing-4); + } + + > .mynah-form-input-switch-label { + display: inline-flex; + flex-flow: row nowrap; + justify-content: flex-start; + align-items: center; + position: relative; + gap: var(--mynah-sizing-1); + cursor: pointer; + overflow: hidden; + + box-sizing: border-box; + min-width: var(--mynah-sizing-9); + min-height: calc(var(--mynah-sizing-6) + var(--mynah-sizing-half)); + padding: var(--mynah-border-width); + + > input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; + &:checked { + & + .mynah-form-input-switch-check { + transform: translateX(var(--mynah-sizing-3)); + border-color: var(--mynah-color-button); + background-color: var(--mynah-color-button); + color: var(--mynah-color-button-reverse); + } + } + } + + > .mynah-form-input-switch-check-bg { + transition: inherit; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: auto; + height: auto; + background-color: var(--mynah-color-bg); + border-radius: inherit; + z-index: var(--mynah-z-1); + border-radius: var(--mynah-input-radius); + border: solid var(--mynah-border-width) var(--mynah-color-border-default); + } + > .mynah-form-input-switch-check { + position: relative; + transition: var(--mynah-bottom-panel-transition); + width: calc(var(--mynah-sizing-6) - (4 * var(--mynah-border-width))); + height: calc(var(--mynah-sizing-6) - (4 * var(--mynah-border-width))); + border-radius: calc(var(--mynah-input-radius) - var(--mynah-border-width)); + overflow: hidden; + box-sizing: border-box; + border: var(--mynah-border-width) solid var(--mynah-color-border-default); + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + background-color: var(--mynah-card-bg); + color: var(--mynah-color-text-weak); + z-index: var(--mynah-z-2); + transform: translateX(calc(2 * var(--mynah-border-width))); + + > .mynah-ui-icon { + position: relative; + transition: inherit; + z-index: var(--mynah-z-2); + font-size: 90%; + } + + & + span:empty { + display: none; + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/components/form-items/_toggle-group.scss b/vendor/mynah-ui/src/styles/components/form-items/_toggle-group.scss new file mode 100644 index 00000000..2b48a249 --- /dev/null +++ b/vendor/mynah-ui/src/styles/components/form-items/_toggle-group.scss @@ -0,0 +1,75 @@ +.mynah-form-input-radio-wrapper { + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + &:not(:last-child) { + margin-right: 0; + } + + > .mynah-form-input-radio-label { + display: inline-flex; + flex-flow: row nowrap; + justify-content: center; + align-items: center; + position: relative; + gap: var(--mynah-sizing-half); + padding: var(--mynah-sizing-1); + cursor: pointer; + > input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; + &:checked + .mynah-form-input-radio-check { + border-color: var(--mynah-color-button); + > .mynah-ui-icon { + transform: scale(1); + color: var(--mynah-color-button-reverse); + } + &::after { + background-color: var(--mynah-color-button); + opacity: 1; + } + & + span { + color: var(--mynah-color-button-reverse); + } + } + } + > .mynah-form-input-radio-check { + position: initial; + width: auto; + height: auto; + border-radius: calc(var(--mynah-input-radius) - var(--mynah-border-width)); + overflow: visible; + box-sizing: border-box; + border: none; + font-size: var(--mynah-font-size-xsmall); + + > .mynah-ui-icon { + transform: scale(1) !important; + color: inherit; + } + &::after { + content: ''; + transition: inherit; + position: absolute; + top: var(--mynah-border-width); + right: var(--mynah-border-width); + bottom: var(--mynah-border-width); + left: var(--mynah-border-width); + height: auto; + width: auto; + background-color: var(--mynah-color-border-default); + opacity: 0; + border-radius: inherit; + z-index: var(--mynah-z-1); + } + & + span { + position: relative; + z-index: var(--mynah-z-1); + } + } + } +} diff --git a/vendor/mynah-ui/src/styles/favicons.scss b/vendor/mynah-ui/src/styles/favicons.scss new file mode 100644 index 00000000..3d948ac0 --- /dev/null +++ b/vendor/mynah-ui/src/styles/favicons.scss @@ -0,0 +1 @@ +// no more favicons for now diff --git a/vendor/mynah-ui/src/styles/styles.scss b/vendor/mynah-ui/src/styles/styles.scss new file mode 100644 index 00000000..6938bfc2 --- /dev/null +++ b/vendor/mynah-ui/src/styles/styles.scss @@ -0,0 +1,31 @@ +@import 'mixins'; +@import 'variables'; +@import 'animations'; +@import 'dark'; +@import 'favicons'; +@import 'scrollbars'; +@import 'splash-loader'; +@import './components/main-container'; +@import './components/detailed-list'; +@import './components/dropdown-list'; +:root { + --mynah-sizing-half: calc(var(--mynah-sizing-base) / 2); + --mynah-sizing-1: var(--mynah-sizing-base); + --mynah-sizing-2: calc(var(--mynah-sizing-base) * 2); + --mynah-sizing-3: calc(var(--mynah-sizing-base) * 3); + --mynah-sizing-4: calc(var(--mynah-sizing-base) * 4); + --mynah-sizing-5: calc(var(--mynah-sizing-base) * 5); + --mynah-sizing-6: calc(var(--mynah-sizing-base) * 6); + --mynah-sizing-7: calc(var(--mynah-sizing-base) * 7); + --mynah-sizing-8: calc(var(--mynah-sizing-base) * 8); + --mynah-sizing-9: calc(var(--mynah-sizing-base) * 9); + --mynah-sizing-10: calc(var(--mynah-sizing-base) * 10); + --mynah-sizing-11: calc(var(--mynah-sizing-base) * 11); + --mynah-sizing-12: calc(var(--mynah-sizing-base) * 12); + --mynah-sizing-13: calc(var(--mynah-sizing-base) * 13); + --mynah-sizing-14: calc(var(--mynah-sizing-base) * 14); + --mynah-sizing-15: calc(var(--mynah-sizing-base) * 15); + --mynah-sizing-16: calc(var(--mynah-sizing-base) * 16); + --mynah-sizing-17: calc(var(--mynah-sizing-base) * 17); + --mynah-sizing-18: calc(var(--mynah-sizing-base) * 18); +} diff --git a/vendor/mynah-ui/src/unescape.d.ts b/vendor/mynah-ui/src/unescape.d.ts new file mode 100644 index 00000000..697f43fa --- /dev/null +++ b/vendor/mynah-ui/src/unescape.d.ts @@ -0,0 +1,4 @@ +declare module 'unescape-html' { + function unescapeHTML (a: string): string; + export = unescapeHTML; +} diff --git a/vendor/mynah-ui/test-config/config.js b/vendor/mynah-ui/test-config/config.js new file mode 100644 index 00000000..d0b9f34f --- /dev/null +++ b/vendor/mynah-ui/test-config/config.js @@ -0,0 +1,3 @@ +global.ResizeObserver = null; +global.MutationObserver = null; +global.IntersectionObserver = null; diff --git a/vendor/mynah-ui/tsconfig.json b/vendor/mynah-ui/tsconfig.json new file mode 100644 index 00000000..a6ced536 --- /dev/null +++ b/vendor/mynah-ui/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "jsx": "react", + "esModuleInterop": true, + "resolveJsonModule": true, + "module": "CommonJS", + "lib": ["ES2019", "ES2015", "DOM"], + "target": "ES2019", + "outDir": "./dist", + "sourceMap": true, + "declaration": true, + "strict": true, + "skipLibCheck": true, + "preserveConstEnums": true, + "typeRoots": ["./node_modules/@types"], + "strictPropertyInitialization": false, + }, + "exclude": ["dist", "node_modules", "docs", "example", "ui-tests"], +} diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside-1.png new file mode 100644 index 00000000..a9493641 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape-1.png new file mode 100644 index 00000000..8d71a37f Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space-1.png new file mode 100644 index 00000000..51519a60 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-filter-context-selector-list/Open-MynahUI-Context-selector-should-filter-context-selector-list-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-filter-context-selector-list/Open-MynahUI-Context-selector-should-filter-context-selector-list-1.png new file mode 100644 index 00000000..2dd924b8 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-filter-context-selector-list/Open-MynahUI-Context-selector-should-filter-context-selector-list-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-render-the-context-selector/Open-MynahUI-Context-selector-should-render-the-context-selector-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-render-the-context-selector/Open-MynahUI-Context-selector-should-render-the-context-selector-1.png new file mode 100644 index 00000000..9c04353e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-render-the-context-selector/Open-MynahUI-Context-selector-should-render-the-context-selector-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking-1.png new file mode 100644 index 00000000..b03354b1 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter-1.png new file mode 100644 index 00000000..b03354b1 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab-1.png new file mode 100644 index 00000000..b03354b1 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-closed.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-closed.png new file mode 100644 index 00000000..16f29b8d Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-closed.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-initial.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-initial.png new file mode 100644 index 00000000..16f29b8d Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-initial.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-open.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-open.png new file mode 100644 index 00000000..e16578a9 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-open.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-final.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-final.png new file mode 100644 index 00000000..117d207b Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-final.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-initial.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-initial.png new file mode 100644 index 00000000..1b735213 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-initial.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-open.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-open.png new file mode 100644 index 00000000..d767b023 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-open.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-cancel-feedback-form/Open-MynahUI-Feedback-form-should-cancel-feedback-form-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-cancel-feedback-form/Open-MynahUI-Feedback-form-should-cancel-feedback-form-1.png new file mode 100644 index 00000000..6a8b0dc5 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-cancel-feedback-form/Open-MynahUI-Feedback-form-should-cancel-feedback-form-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-downvote-results/Open-MynahUI-Feedback-form-should-render-downvote-results-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-downvote-results/Open-MynahUI-Feedback-form-should-render-downvote-results-1.png new file mode 100644 index 00000000..786bef82 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-downvote-results/Open-MynahUI-Feedback-form-should-render-downvote-results-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-feedback-form/Open-MynahUI-Feedback-form-should-render-feedback-form-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-feedback-form/Open-MynahUI-Feedback-form-should-render-feedback-form-1.png new file mode 100644 index 00000000..2b8f0b71 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-feedback-form/Open-MynahUI-Feedback-form-should-render-feedback-form-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-upvote-results/Open-MynahUI-Feedback-form-should-render-upvote-results-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-upvote-results/Open-MynahUI-Feedback-form-should-render-upvote-results-1.png new file mode 100644 index 00000000..3472f8ee Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-upvote-results/Open-MynahUI-Feedback-form-should-render-upvote-results-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-vote-buttons/Open-MynahUI-Feedback-form-should-render-vote-buttons-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-vote-buttons/Open-MynahUI-Feedback-form-should-render-vote-buttons-1.png new file mode 100644 index 00000000..c1bc55ca Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-render-vote-buttons/Open-MynahUI-Feedback-form-should-render-vote-buttons-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-submit-feedback-form/Open-MynahUI-Feedback-form-should-submit-feedback-form-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-submit-feedback-form/Open-MynahUI-Feedback-form-should-submit-feedback-form-1.png new file mode 100644 index 00000000..4a8c9862 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Feedback-form-should-submit-feedback-form/Open-MynahUI-Feedback-form-should-submit-feedback-form-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-pills-should-render-deleted-files-with-special-styling/file-pills-with-deleted-files.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-pills-should-render-deleted-files-with-special-styling/file-pills-with-deleted-files.png new file mode 100644 index 00000000..38618bd1 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-pills-should-render-deleted-files-with-special-styling/file-pills-with-deleted-files.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-pills-should-render-file-pills-in-header/file-pills-basic.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-pills-should-render-file-pills-in-header/file-pills-basic.png new file mode 100644 index 00000000..96158000 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-pills-should-render-file-pills-in-header/file-pills-basic.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-1.png new file mode 100644 index 00000000..d892ed5f Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-2.png new file mode 100644 index 00000000..75b4b56d Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-3.png new file mode 100644 index 00000000..6af71141 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details-1.png new file mode 100644 index 00000000..3653fe1c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-file-tree/Open-MynahUI-File-tree-should-show-file-tree-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-file-tree/Open-MynahUI-File-tree-should-show-file-tree-1.png new file mode 100644 index 00000000..6af71141 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-file-tree/Open-MynahUI-File-tree-should-show-file-tree-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-1.png new file mode 100644 index 00000000..5c30e287 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-2.png new file mode 100644 index 00000000..0445dadf Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-1.png new file mode 100644 index 00000000..1a840af2 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-2.png new file mode 100644 index 00000000..6ecb6486 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-3.png new file mode 100644 index 00000000..0361398f Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-disable-forms-on-submit/Open-MynahUI-Forms-should-disable-forms-on-submit-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-disable-forms-on-submit/Open-MynahUI-Forms-should-disable-forms-on-submit-1.png new file mode 100644 index 00000000..dcc6a81b Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-disable-forms-on-submit/Open-MynahUI-Forms-should-disable-forms-on-submit-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-remove-form-card-when-canceled/Open-MynahUI-Forms-should-remove-form-card-when-canceled-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-remove-form-card-when-canceled/Open-MynahUI-Forms-should-remove-form-card-when-canceled-1.png new file mode 100644 index 00000000..30301892 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-remove-form-card-when-canceled/Open-MynahUI-Forms-should-remove-form-card-when-canceled-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-render-form-elements-correctly/Open-MynahUI-Forms-should-render-form-elements-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-render-form-elements-correctly/Open-MynahUI-Forms-should-render-form-elements-correctly-1.png new file mode 100644 index 00000000..68803771 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Forms-should-render-form-elements-correctly/Open-MynahUI-Forms-should-render-form-elements-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-1.png new file mode 100644 index 00000000..56c664fe Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-2.png new file mode 100644 index 00000000..9bede782 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-1.png new file mode 100644 index 00000000..d32a81ce Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-2.png new file mode 100644 index 00000000..18c9b582 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-3.png new file mode 100644 index 00000000..66142ecb Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-4.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-4.png new file mode 100644 index 00000000..638c1923 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-4.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-5.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-5.png new file mode 100644 index 00000000..a0a9b5cc Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-5.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-6.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-6.png new file mode 100644 index 00000000..d65015ac Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-6.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-1.png new file mode 100644 index 00000000..0af913dc Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-2.png new file mode 100644 index 00000000..8245de1c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items-1.png new file mode 100644 index 00000000..6897cb5e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment-1.png new file mode 100644 index 00000000..df9c57bd Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-1.png new file mode 100644 index 00000000..a0495991 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt-1.png new file mode 100644 index 00000000..9b8a9ff4 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt-1.png new file mode 100644 index 00000000..f51f0b99 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt-1.png new file mode 100644 index 00000000..135c4b45 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt-1.png new file mode 100644 index 00000000..a9536ce1 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-handle-quick-action-commands-header-interaction/quick-action-commands-header-hover.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-handle-quick-action-commands-header-interaction/quick-action-commands-header-hover.png new file mode 100644 index 00000000..f7723fd3 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-handle-quick-action-commands-header-interaction/quick-action-commands-header-hover.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-not-render-header-when-not-applicable/quick-action-commands-header-not-present.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-not-render-header-when-not-applicable/quick-action-commands-header-not-present.png new file mode 100644 index 00000000..9c04353e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-not-render-header-when-not-applicable/quick-action-commands-header-not-present.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-render-header-with-correct-status-styling/quick-action-commands-header-status.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-render-header-with-correct-status-styling/quick-action-commands-header-status.png new file mode 100644 index 00000000..f7723fd3 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-render-header-with-correct-status-styling/quick-action-commands-header-status.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-render-the-quick-action-commands-header/quick-action-commands-header.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-render-the-quick-action-commands-header/quick-action-commands-header.png new file mode 100644 index 00000000..f7723fd3 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-Action-Commands-Header-should-render-the-quick-action-commands-header/quick-action-commands-header.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside-1.png new file mode 100644 index 00000000..3549c6ae Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape-1.png new file mode 100644 index 00000000..770ecd53 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space-1.png new file mode 100644 index 00000000..57a0320e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list-1.png new file mode 100644 index 00000000..cba1f010 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector-1.png new file mode 100644 index 00000000..f7723fd3 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking-1.png new file mode 100644 index 00000000..dc52b776 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter-1.png new file mode 100644 index 00000000..e3db919b Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space-1.png new file mode 100644 index 00000000..45d674cc Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab-1.png new file mode 100644 index 00000000..45d674cc Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Tabs-should-close-the-tab/Open-MynahUI-Tabs-should-close-the-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Tabs-should-close-the-tab/Open-MynahUI-Tabs-should-close-the-tab-1.png new file mode 100644 index 00000000..0d07c727 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Tabs-should-close-the-tab/Open-MynahUI-Tabs-should-close-the-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Tabs-should-open-a-new-tab/Open-MynahUI-Tabs-should-open-a-new-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Tabs-should-open-a-new-tab/Open-MynahUI-Tabs-should-open-a-new-tab-1.png new file mode 100644 index 00000000..b6b1ca52 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-Tabs-should-open-a-new-tab/Open-MynahUI-Tabs-should-open-a-new-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-display-context-command-disabled-text/Open-MynahUI-should-display-context-command-disabled-text-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-display-context-command-disabled-text/Open-MynahUI-should-display-context-command-disabled-text-1.png new file mode 100644 index 00000000..9ac48a90 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-display-context-command-disabled-text/Open-MynahUI-should-display-context-command-disabled-text-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-1.png new file mode 100644 index 00000000..ed85e0f5 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-2.png new file mode 100644 index 00000000..604f57f3 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-3.png new file mode 100644 index 00000000..76b035ff Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-parse-markdown/Open-MynahUI-should-parse-markdown-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-parse-markdown/Open-MynahUI-should-parse-markdown-1.png new file mode 100644 index 00000000..6530c010 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-parse-markdown/Open-MynahUI-should-parse-markdown-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-and-remove-dismissible-cards/Open-MynahUI-should-render-and-remove-dismissible-cards-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-and-remove-dismissible-cards/Open-MynahUI-should-render-and-remove-dismissible-cards-1.png new file mode 100644 index 00000000..16c62f58 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-and-remove-dismissible-cards/Open-MynahUI-should-render-and-remove-dismissible-cards-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-1.png new file mode 100644 index 00000000..953519ff Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-2.png new file mode 100644 index 00000000..104c7f46 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-1.png new file mode 100644 index 00000000..c64eab3d Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-2.png new file mode 100644 index 00000000..d78c18a2 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-3.png new file mode 100644 index 00000000..506e8c0d Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-character-limit-counter/Open-MynahUI-should-render-character-limit-counter-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-character-limit-counter/Open-MynahUI-should-render-character-limit-counter-1.png new file mode 100644 index 00000000..8cdbc17e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-character-limit-counter/Open-MynahUI-should-render-character-limit-counter-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-1.png new file mode 100644 index 00000000..2a5ff5fb Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-2.png new file mode 100644 index 00000000..878f9f68 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-information-cards-correctly/Open-MynahUI-should-render-information-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-information-cards-correctly/Open-MynahUI-should-render-information-cards-correctly-1.png new file mode 100644 index 00000000..7296f7d6 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-information-cards-correctly/Open-MynahUI-should-render-information-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-initial-data/Open-MynahUI-should-render-initial-data-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-initial-data/Open-MynahUI-should-render-initial-data-1.png new file mode 100644 index 00000000..770ecd53 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-initial-data/Open-MynahUI-should-render-initial-data-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-1.png new file mode 100644 index 00000000..93486290 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-2.png new file mode 100644 index 00000000..524b5ba2 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-new-card-when-followup-click/Open-MynahUI-should-render-new-card-when-followup-click-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-new-card-when-followup-click/Open-MynahUI-should-render-new-card-when-followup-click-1.png new file mode 100644 index 00000000..4c6f8b60 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-new-card-when-followup-click/Open-MynahUI-should-render-new-card-when-followup-click-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-1.png new file mode 100644 index 00000000..b89c291b Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-2.png new file mode 100644 index 00000000..2d656094 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-user-prompt/Open-MynahUI-should-render-user-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-user-prompt/Open-MynahUI-should-render-user-prompt-1.png new file mode 100644 index 00000000..4e85f200 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-user-prompt/Open-MynahUI-should-render-user-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-1.png new file mode 100644 index 00000000..47491d3b Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-2.png new file mode 100644 index 00000000..292ecf3a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-1.png new file mode 100644 index 00000000..ee1f5c0c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-2.png new file mode 100644 index 00000000..5cbe346a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-3.png new file mode 100644 index 00000000..ee1f5c0c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-progress-indicator/Open-MynahUI-should-show-progress-indicator-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-progress-indicator/Open-MynahUI-should-show-progress-indicator-1.png new file mode 100644 index 00000000..33dc97ae Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-progress-indicator/Open-MynahUI-should-show-progress-indicator-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-prompt-options/Open-MynahUI-should-show-prompt-options-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-prompt-options/Open-MynahUI-should-show-prompt-options-1.png new file mode 100644 index 00000000..ac4fbade Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/chromium/Open-MynahUI-should-show-prompt-options/Open-MynahUI-should-show-prompt-options-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside-1.png new file mode 100644 index 00000000..0f38abfb Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside/Open-MynahUI-Context-selector-should-close-the-context-selector-by-clicking-outside-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape-1.png new file mode 100644 index 00000000..56037306 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-escape-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space-1.png new file mode 100644 index 00000000..a9fee7a0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space/Open-MynahUI-Context-selector-should-close-the-context-selector-by-pressing-space-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-filter-context-selector-list/Open-MynahUI-Context-selector-should-filter-context-selector-list-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-filter-context-selector-list/Open-MynahUI-Context-selector-should-filter-context-selector-list-1.png new file mode 100644 index 00000000..391068b9 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-filter-context-selector-list/Open-MynahUI-Context-selector-should-filter-context-selector-list-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-render-the-context-selector/Open-MynahUI-Context-selector-should-render-the-context-selector-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-render-the-context-selector/Open-MynahUI-Context-selector-should-render-the-context-selector-1.png new file mode 100644 index 00000000..13480c51 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-render-the-context-selector/Open-MynahUI-Context-selector-should-render-the-context-selector-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking-1.png new file mode 100644 index 00000000..ae341a07 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking/Open-MynahUI-Context-selector-should-select-context-selector-item-by-clicking-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter-1.png new file mode 100644 index 00000000..ae341a07 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter/Open-MynahUI-Context-selector-should-select-context-selector-item-with-enter-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab-1.png new file mode 100644 index 00000000..ae341a07 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab/Open-MynahUI-Context-selector-should-select-context-selector-item-with-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-closed.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-closed.png new file mode 100644 index 00000000..40c27bf4 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-closed.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-initial.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-initial.png new file mode 100644 index 00000000..40c27bf4 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-initial.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-open.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-open.png new file mode 100644 index 00000000..3c4da497 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-open-and-close-dropdown/dropdown-open.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-final.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-final.png new file mode 100644 index 00000000..ed83dbdb Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-final.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-initial.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-initial.png new file mode 100644 index 00000000..44d3aa68 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-initial.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-open.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-open.png new file mode 100644 index 00000000..43cc01b9 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Dropdown-list-should-select-dropdown-option/dropdown-select-open.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-cancel-feedback-form/Open-MynahUI-Feedback-form-should-cancel-feedback-form-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-cancel-feedback-form/Open-MynahUI-Feedback-form-should-cancel-feedback-form-1.png new file mode 100644 index 00000000..1b8e210a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-cancel-feedback-form/Open-MynahUI-Feedback-form-should-cancel-feedback-form-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-downvote-results/Open-MynahUI-Feedback-form-should-render-downvote-results-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-downvote-results/Open-MynahUI-Feedback-form-should-render-downvote-results-1.png new file mode 100644 index 00000000..c05189ba Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-downvote-results/Open-MynahUI-Feedback-form-should-render-downvote-results-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-feedback-form/Open-MynahUI-Feedback-form-should-render-feedback-form-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-feedback-form/Open-MynahUI-Feedback-form-should-render-feedback-form-1.png new file mode 100644 index 00000000..3d2192a0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-feedback-form/Open-MynahUI-Feedback-form-should-render-feedback-form-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-upvote-results/Open-MynahUI-Feedback-form-should-render-upvote-results-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-upvote-results/Open-MynahUI-Feedback-form-should-render-upvote-results-1.png new file mode 100644 index 00000000..9d4b8dad Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-upvote-results/Open-MynahUI-Feedback-form-should-render-upvote-results-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-vote-buttons/Open-MynahUI-Feedback-form-should-render-vote-buttons-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-vote-buttons/Open-MynahUI-Feedback-form-should-render-vote-buttons-1.png new file mode 100644 index 00000000..355ef6a3 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-render-vote-buttons/Open-MynahUI-Feedback-form-should-render-vote-buttons-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-submit-feedback-form/Open-MynahUI-Feedback-form-should-submit-feedback-form-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-submit-feedback-form/Open-MynahUI-Feedback-form-should-submit-feedback-form-1.png new file mode 100644 index 00000000..0de45b6c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Feedback-form-should-submit-feedback-form/Open-MynahUI-Feedback-form-should-submit-feedback-form-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-pills-should-render-deleted-files-with-special-styling/file-pills-with-deleted-files.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-pills-should-render-deleted-files-with-special-styling/file-pills-with-deleted-files.png new file mode 100644 index 00000000..8fcc0d7a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-pills-should-render-deleted-files-with-special-styling/file-pills-with-deleted-files.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-pills-should-render-file-pills-in-header/file-pills-basic.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-pills-should-render-file-pills-in-header/file-pills-basic.png new file mode 100644 index 00000000..c694ee9e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-pills-should-render-file-pills-in-header/file-pills-basic.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-1.png new file mode 100644 index 00000000..5ee3bc5a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-2.png new file mode 100644 index 00000000..218e1878 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-3.png new file mode 100644 index 00000000..0c0945f4 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders/Open-MynahUI-File-tree-should-collapse-and-expand-file-in-folders-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details-1.png new file mode 100644 index 00000000..a526526e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details/Open-MynahUI-File-tree-should-render-file-appearance-based-on-its-details-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-file-tree/Open-MynahUI-File-tree-should-show-file-tree-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-file-tree/Open-MynahUI-File-tree-should-show-file-tree-1.png new file mode 100644 index 00000000..0c0945f4 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-file-tree/Open-MynahUI-File-tree-should-show-file-tree-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-1.png new file mode 100644 index 00000000..ea9711ab Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-2.png new file mode 100644 index 00000000..2c50d9d0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover/Open-MynahUI-File-tree-should-show-tooltip-with-file-description-on-hover-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-1.png new file mode 100644 index 00000000..03625acc Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-2.png new file mode 100644 index 00000000..639819b7 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-3.png new file mode 100644 index 00000000..32e87a6e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click/Open-MynahUI-File-tree-should-trigger-default-or-sub-action-on-click-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-disable-forms-on-submit/Open-MynahUI-Forms-should-disable-forms-on-submit-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-disable-forms-on-submit/Open-MynahUI-Forms-should-disable-forms-on-submit-1.png new file mode 100644 index 00000000..95836e83 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-disable-forms-on-submit/Open-MynahUI-Forms-should-disable-forms-on-submit-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-remove-form-card-when-canceled/Open-MynahUI-Forms-should-remove-form-card-when-canceled-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-remove-form-card-when-canceled/Open-MynahUI-Forms-should-remove-form-card-when-canceled-1.png new file mode 100644 index 00000000..7aedd39d Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-remove-form-card-when-canceled/Open-MynahUI-Forms-should-remove-form-card-when-canceled-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-render-form-elements-correctly/Open-MynahUI-Forms-should-render-form-elements-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-render-form-elements-correctly/Open-MynahUI-Forms-should-render-form-elements-correctly-1.png new file mode 100644 index 00000000..ff4a6845 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Forms-should-render-form-elements-correctly/Open-MynahUI-Forms-should-render-form-elements-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-1.png new file mode 100644 index 00000000..66bc52ca Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-2.png new file mode 100644 index 00000000..65c18ac0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly/Open-MynahUI-Prompt-Top-Bar-should-manage-context-items-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-1.png new file mode 100644 index 00000000..2bd3e3c3 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-2.png new file mode 100644 index 00000000..c62e712a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-3.png new file mode 100644 index 00000000..05e791a4 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-4.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-4.png new file mode 100644 index 00000000..9cd3d5ec Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-4.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-5.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-5.png new file mode 100644 index 00000000..5fe576dc Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-5.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-6.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-6.png new file mode 100644 index 00000000..c2893356 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button/Open-MynahUI-Prompt-Top-Bar-should-render-prompt-top-bar-with-title-context-items-and-button-6.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-1.png new file mode 100644 index 00000000..7317e1c2 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-2.png new file mode 100644 index 00000000..73339a83 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button/Open-MynahUI-Prompt-Top-Bar-should-show-overlay-when-clicking-top-bar-button-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items-1.png new file mode 100644 index 00000000..84119262 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items/Open-MynahUI-Prompt-Top-Bar-should-show-tooltip-when-hovering-over-pinned-context-items-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment-1.png new file mode 100644 index 00000000..b8e38f02 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-with-code-attachment-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-1.png new file mode 100644 index 00000000..92872f91 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt/Open-MynahUI-Prompt-navigation-should-navigate-back-to-current-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt-1.png new file mode 100644 index 00000000..9637a5e6 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-current-empty-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt-1.png new file mode 100644 index 00000000..92e11293 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt/Open-MynahUI-Prompt-navigation-should-navigate-down-to-next-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt-1.png new file mode 100644 index 00000000..dc891e4c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt/Open-MynahUI-Prompt-navigation-should-navigate-up-to-previous-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt-1.png new file mode 100644 index 00000000..28375592 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt/Open-MynahUI-Prompt-navigation-should-stay-on-current-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-handle-quick-action-commands-header-interaction/quick-action-commands-header-hover.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-handle-quick-action-commands-header-interaction/quick-action-commands-header-hover.png new file mode 100644 index 00000000..07075716 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-handle-quick-action-commands-header-interaction/quick-action-commands-header-hover.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-not-render-header-when-not-applicable/quick-action-commands-header-not-present.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-not-render-header-when-not-applicable/quick-action-commands-header-not-present.png new file mode 100644 index 00000000..13480c51 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-not-render-header-when-not-applicable/quick-action-commands-header-not-present.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-render-header-with-correct-status-styling/quick-action-commands-header-status.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-render-header-with-correct-status-styling/quick-action-commands-header-status.png new file mode 100644 index 00000000..07075716 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-render-header-with-correct-status-styling/quick-action-commands-header-status.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-render-the-quick-action-commands-header/quick-action-commands-header.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-render-the-quick-action-commands-header/quick-action-commands-header.png new file mode 100644 index 00000000..07075716 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-Action-Commands-Header-should-render-the-quick-action-commands-header/quick-action-commands-header.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside-1.png new file mode 100644 index 00000000..1d09105c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-clicking-outside-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape-1.png new file mode 100644 index 00000000..c70d371a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-escape-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space-1.png new file mode 100644 index 00000000..f0a41df0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space/Open-MynahUI-Quick-command-selector-should-close-the-quick-command-selector-by-pressing-space-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list-1.png new file mode 100644 index 00000000..047e26ac Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list/Open-MynahUI-Quick-command-selector-should-filter-quick-command-selector-list-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector-1.png new file mode 100644 index 00000000..07075716 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector/Open-MynahUI-Quick-command-selector-should-render-the-quick-command-selector-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking-1.png new file mode 100644 index 00000000..c69b7d27 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-by-clicking-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter-1.png new file mode 100644 index 00000000..c2ac4421 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-enter-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space-1.png new file mode 100644 index 00000000..70a885bf Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-space-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab-1.png new file mode 100644 index 00000000..70a885bf Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab/Open-MynahUI-Quick-command-selector-should-select-quick-command-selector-item-with-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Tabs-should-close-the-tab/Open-MynahUI-Tabs-should-close-the-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Tabs-should-close-the-tab/Open-MynahUI-Tabs-should-close-the-tab-1.png new file mode 100644 index 00000000..028b874a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Tabs-should-close-the-tab/Open-MynahUI-Tabs-should-close-the-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Tabs-should-open-a-new-tab/Open-MynahUI-Tabs-should-open-a-new-tab-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Tabs-should-open-a-new-tab/Open-MynahUI-Tabs-should-open-a-new-tab-1.png new file mode 100644 index 00000000..552c25b0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-Tabs-should-open-a-new-tab/Open-MynahUI-Tabs-should-open-a-new-tab-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-display-context-command-disabled-text/Open-MynahUI-should-display-context-command-disabled-text-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-display-context-command-disabled-text/Open-MynahUI-should-display-context-command-disabled-text-1.png new file mode 100644 index 00000000..b1b748cb Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-display-context-command-disabled-text/Open-MynahUI-should-display-context-command-disabled-text-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-1.png new file mode 100644 index 00000000..e461a3eb Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-2.png new file mode 100644 index 00000000..0d9671e5 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-3.png new file mode 100644 index 00000000..9fe7dbfb Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-keep-the-content-inside-window-boundaries/Open-MynahUI-should-keep-the-content-inside-window-boundaries-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-parse-markdown/Open-MynahUI-should-parse-markdown-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-parse-markdown/Open-MynahUI-should-parse-markdown-1.png new file mode 100644 index 00000000..b54d4703 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-parse-markdown/Open-MynahUI-should-parse-markdown-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-and-remove-dismissible-cards/Open-MynahUI-should-render-and-remove-dismissible-cards-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-and-remove-dismissible-cards/Open-MynahUI-should-render-and-remove-dismissible-cards-1.png new file mode 100644 index 00000000..719ad219 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-and-remove-dismissible-cards/Open-MynahUI-should-render-and-remove-dismissible-cards-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-1.png new file mode 100644 index 00000000..5c4bacc9 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-2.png new file mode 100644 index 00000000..e821a824 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-buttons-on-cards-correctly/Open-MynahUI-should-render-buttons-on-cards-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-1.png new file mode 100644 index 00000000..8ee2a067 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-2.png new file mode 100644 index 00000000..9270643b Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-3.png new file mode 100644 index 00000000..f1907a77 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-card-headers-correctly/Open-MynahUI-should-render-card-headers-correctly-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-character-limit-counter/Open-MynahUI-should-render-character-limit-counter-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-character-limit-counter/Open-MynahUI-should-render-character-limit-counter-1.png new file mode 100644 index 00000000..f49b372e Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-character-limit-counter/Open-MynahUI-should-render-character-limit-counter-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-1.png new file mode 100644 index 00000000..0278eeef Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-2.png new file mode 100644 index 00000000..878f9f68 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-custom-icons-correctly/Open-MynahUI-should-render-custom-icons-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-information-cards-correctly/Open-MynahUI-should-render-information-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-information-cards-correctly/Open-MynahUI-should-render-information-cards-correctly-1.png new file mode 100644 index 00000000..3d115df5 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-information-cards-correctly/Open-MynahUI-should-render-information-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-initial-data/Open-MynahUI-should-render-initial-data-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-initial-data/Open-MynahUI-should-render-initial-data-1.png new file mode 100644 index 00000000..c70d371a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-initial-data/Open-MynahUI-should-render-initial-data-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-1.png new file mode 100644 index 00000000..082d287c Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-2.png new file mode 100644 index 00000000..8072014a Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-muted-cards-correctly/Open-MynahUI-should-render-muted-cards-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-new-card-when-followup-click/Open-MynahUI-should-render-new-card-when-followup-click-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-new-card-when-followup-click/Open-MynahUI-should-render-new-card-when-followup-click-1.png new file mode 100644 index 00000000..245769a7 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-new-card-when-followup-click/Open-MynahUI-should-render-new-card-when-followup-click-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-1.png new file mode 100644 index 00000000..87ab05e6 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-2.png new file mode 100644 index 00000000..5b9f83f8 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-tabbed-cards-correctly/Open-MynahUI-should-render-tabbed-cards-correctly-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-user-prompt/Open-MynahUI-should-render-user-prompt-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-user-prompt/Open-MynahUI-should-render-user-prompt-1.png new file mode 100644 index 00000000..622d17f5 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-user-prompt/Open-MynahUI-should-render-user-prompt-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-1.png new file mode 100644 index 00000000..3e64b4c5 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-2.png new file mode 100644 index 00000000..cb0c8295 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-render-welcome-structure/Open-MynahUI-should-render-welcome-structure-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-1.png new file mode 100644 index 00000000..284173d0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-2.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-2.png new file mode 100644 index 00000000..65cdc888 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-2.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-3.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-3.png new file mode 100644 index 00000000..284173d0 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover/Open-MynahUI-should-show-link-preview-in-tooltip-on-link-hover-3.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-progress-indicator/Open-MynahUI-should-show-progress-indicator-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-progress-indicator/Open-MynahUI-should-show-progress-indicator-1.png new file mode 100644 index 00000000..a8f60d66 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-progress-indicator/Open-MynahUI-should-show-progress-indicator-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-prompt-options/Open-MynahUI-should-show-prompt-options-1.png b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-prompt-options/Open-MynahUI-should-show-prompt-options-1.png new file mode 100644 index 00000000..a69abdaf Binary files /dev/null and b/vendor/mynah-ui/ui-tests/__snapshots__/webkit/Open-MynahUI-should-show-prompt-options/Open-MynahUI-should-show-prompt-options-1.png differ diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/click-followup.ts b/vendor/mynah-ui/ui-tests/__test__/flows/click-followup.ts new file mode 100644 index 00000000..358986e0 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/click-followup.ts @@ -0,0 +1,23 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const clickToFollowup = async (page: Page, skipScreenshots?: boolean): Promise => { + const followupMessageSelector = `${getSelector(testIds.chatItem.type.answer)}[messageid="mynah-ui-test-followup"]`; + await page.waitForSelector(followupMessageSelector); + await waitForAnimationEnd(page); + + await page.locator(`${getSelector(testIds.chatItem.chatItemFollowup.optionButton)}:nth-child(1)`).click(); + await page.mouse.move(0, 0); + + await waitForAnimationEnd(page); + const userCard = await page.waitForSelector(getSelector(testIds.chatItem.type.prompt)); + expect(userCard).toBeDefined(); + await waitForAnimationEnd(page); + await userCard.scrollIntoViewIfNeeded(); + await justWait(50); + + if (skipScreenshots !== true) { + expect(await userCard.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/close-tab.ts b/vendor/mynah-ui/ui-tests/__test__/flows/close-tab.ts new file mode 100644 index 00000000..2ef033c4 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/close-tab.ts @@ -0,0 +1,20 @@ +import { expect, Page } from 'playwright/test'; +import testIds from '../../../src/helper/test-ids'; +import { getSelector } from '../helpers'; + +export const closeTab = async (page: Page, withDblClick?: boolean, skipScreenshots?: boolean): Promise => { + const firstTab = page.locator(`${getSelector(testIds.tabBar.tabOptionWrapper)}:nth-child(1)`); + expect(firstTab).toBeDefined(); + + if (withDblClick !== true) { + await page.locator(getSelector(testIds.tabBar.tabOptionCloseButton)).click(); + } else { + await page.locator(getSelector(testIds.tabBar.tabOptionLabel)).click({ position: { x: 10, y: 10 }, button: 'middle' }); + } + await page.mouse.move(0, 0); + + if (skipScreenshots !== true) { + // No tabs snap + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/disabled-text.ts b/vendor/mynah-ui/ui-tests/__test__/flows/disabled-text.ts new file mode 100644 index 00000000..009f51a3 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/disabled-text.ts @@ -0,0 +1,51 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const renderDisabledText = async (page: Page, skipScreenshots?: boolean): Promise => { + await waitForAnimationEnd(page); + + // Modify existing context commands to add disabled text + await page.evaluate(() => { + const tabData = window.mynahUI.getTabData('tab-1'); + const contextCommands = tabData.getStore()?.contextCommands; + + if (contextCommands !== undefined) { + // Modify the @workspace command to add disabledText + const updatedCommands = contextCommands.map(group => { + const updatedGroup = { ...group }; + if (group.commands !== undefined) { + updatedGroup.commands = group.commands.map(cmd => + cmd.command === '@workspace' + ? { ...cmd, disabledText: 'pending', disabled: true } + : cmd + ); + } + return updatedGroup; + }); + + window.mynahUI.updateStore('tab-1', { + contextCommands: updatedCommands + }); + } + }); + + await waitForAnimationEnd(page); + + // Clear the input and open context selector + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await input.press('@'); + await waitForAnimationEnd(page); + + // Wait for the context selector to be visible + const contextSelector = page.locator(getSelector(testIds.prompt.quickPicksWrapper)).nth(-1); + await expect(contextSelector).toBeVisible(); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + await input.press('Backspace'); + await waitForAnimationEnd(page); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/dismissible-cards.ts b/vendor/mynah-ui/ui-tests/__test__/flows/dismissible-cards.ts new file mode 100644 index 00000000..b79d6afa --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/dismissible-cards.ts @@ -0,0 +1,47 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; +import { ChatItemType } from '../../../dist/main'; + +export const renderAndDismissCard = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate((body) => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + title: 'SAVE THE DATE', + header: { + icon: 'calendar', + iconStatus: 'primary', + body: '## Soon, a new version will be released!', + }, + fullWidth: true, + canBeDismissed: true, + body: "We're improving the performance, adding new features or making new UX changes every week. Save the date for new updates!.", + } + ); + } + }); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const locator = page.locator(answerCardSelector).nth(0); + await locator.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator.screenshot()).toMatchSnapshot(); + } + + // Click the dismiss button to remove the card + const dismissButtonSelector = getSelector(testIds.chatItem.dismissButton); + await page.locator(dismissButtonSelector).nth(0).click(); + await waitForAnimationEnd(page); + + // Verify that the card is now gone + const cardSelector = getSelector(testIds.chatItem.type.answer); + const cardCount = await page.locator(cardSelector).count(); + expect(cardCount).toBe(0); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/dropdown-list/open-close-dropdown.ts b/vendor/mynah-ui/ui-tests/__test__/flows/dropdown-list/open-close-dropdown.ts new file mode 100644 index 00000000..fd9dceb0 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/dropdown-list/open-close-dropdown.ts @@ -0,0 +1,84 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { ChatItemType } from '../../../../dist/static'; + +export const openCloseDropdown = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + snapToTop: true, + body: 'Here is a test dropdown:', + quickSettings: { + type: 'select', + description: 'Choose one of the following options', + tabId: selectedTabId, + messageId: 'dropdown-test-message', + options: [ + { id: 'option1', label: 'Option 1', value: 'option1', selected: false }, + { id: 'option2', label: 'Option 2', value: 'option2', selected: true }, + { id: 'option3', label: 'Option 3', value: 'option3', selected: false } + ] + } + }); + } + }); + + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + // Find the dropdown wrapper using test ID + const dropdownWrapper = page.locator(getSelector(testIds.dropdownList.wrapper)).first(); + expect(dropdownWrapper).toBeDefined(); + + // Find the dropdown button using test ID + const dropdownButton = page.locator(getSelector(testIds.dropdownList.button)); + expect(dropdownButton).toBeDefined(); + + // Verify initial button text shows selected option + await expect(dropdownButton.locator('.mynah-button-label')).toHaveText('Option 2'); + + // Take initial screenshot + expect(await page.screenshot()).toMatchSnapshot('dropdown-initial.png'); + + // Click to open dropdown + await dropdownButton.click(); + await waitForAnimationEnd(page); + + // Verify dropdown portal is created and visible + const dropdownPortal = page.locator(getSelector(testIds.dropdownList.portal)); + expect(dropdownPortal).toBeDefined(); + + // Verify dropdown content + const dropdownDescription = dropdownPortal.locator(getSelector(testIds.dropdownList.description)); + await expect(dropdownDescription).toHaveText('Choose one of the following options'); + + // Verify options are present + const options = dropdownPortal.locator(getSelector(testIds.dropdownList.option)); + await expect(options).toHaveCount(3); + + // Verify selected option has check mark + const selectedOption = dropdownPortal.locator('.mynah-dropdown-list-option.selected'); + await expect(selectedOption).toHaveCount(1); + await expect(selectedOption.locator(getSelector(testIds.dropdownList.optionLabel))).toContainText('Option 2'); + + // Take screenshot of open dropdown + expect(await page.screenshot()).toMatchSnapshot('dropdown-open.png'); + + // Click outside to close dropdown + await page.click('body', { position: { x: 10, y: 10 } }); + await waitForAnimationEnd(page); + + // Verify dropdown is closed + await expect(dropdownPortal).toHaveCount(0); + + // Take final screenshot + expect(await page.screenshot()).toMatchSnapshot('dropdown-closed.png'); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/dropdown-list/select-dropdown-option.ts b/vendor/mynah-ui/ui-tests/__test__/flows/dropdown-list/select-dropdown-option.ts new file mode 100644 index 00000000..b5aba80b --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/dropdown-list/select-dropdown-option.ts @@ -0,0 +1,95 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { ChatItemType } from '../../../../dist/static'; + +export const selectDropdownOption = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + snapToTop: true, + body: 'Test dropdown option selection:', + quickSettings: { + type: 'select', + tabId: selectedTabId, + messageId: 'dropdown-select-test-message', + options: [ + { id: 'opt1', label: 'First Choice', value: 'opt1', selected: true }, + { id: 'opt2', label: 'Second Choice', value: 'opt2', selected: false }, + { id: 'opt3', label: 'Third Choice', value: 'opt3', selected: false } + ] + } + }); + } + }); + + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + // Find the dropdown wrapper using test ID + const dropdownWrapper = page.locator(getSelector(testIds.dropdownList.wrapper)).first(); + expect(dropdownWrapper).toBeDefined(); + + // Find the dropdown button using test ID + const dropdownButton = page.locator(getSelector(testIds.dropdownList.button)); + expect(dropdownButton).toBeDefined(); + + // Verify initial button text shows selected option + await expect(dropdownButton.locator('.mynah-button-label')).toHaveText('First Choice'); + + // Take initial screenshot + expect(await page.screenshot()).toMatchSnapshot('dropdown-select-initial.png'); + + // Open dropdown + await dropdownButton.click(); + await waitForAnimationEnd(page); + + // Verify dropdown portal is created and visible + const dropdownPortal = page.locator(getSelector(testIds.dropdownList.portal)); + expect(dropdownPortal).toBeDefined(); + + // Take screenshot of open dropdown + expect(await page.screenshot()).toMatchSnapshot('dropdown-select-open.png'); + + // Verify first option is selected (has check mark) + const firstOption = dropdownPortal.locator(`${getSelector(testIds.dropdownList.option)}[data-item-id="opt1"]`); + expect(firstOption).toBeDefined(); + const firstCheckIcon = firstOption.locator(getSelector(testIds.dropdownList.checkIcon)); + expect(firstCheckIcon).toBeDefined(); + + // Click on the second option + const secondOption = dropdownPortal.locator(`${getSelector(testIds.dropdownList.option)}[data-item-id="opt2"]`); + expect(secondOption).toBeDefined(); + await secondOption.click(); + await waitForAnimationEnd(page); + + // Verify dropdown is closed after selection + await expect(dropdownPortal).toHaveCount(0); + + // Verify button text updated to show new selection + await expect(dropdownButton.locator('.mynah-button-label')).toHaveText('Second Choice'); + + // Take final screenshot + expect(await page.screenshot()).toMatchSnapshot('dropdown-select-final.png'); + + // Open dropdown again to verify selection persisted + await dropdownButton.click(); + await waitForAnimationEnd(page); + + // Verify second option is now selected + const secondOptionSelected = dropdownPortal.locator(`${getSelector(testIds.dropdownList.option)}[data-item-id="opt2"]`); + expect(secondOptionSelected).toBeDefined(); + const secondCheckIcon = secondOptionSelected.locator(getSelector(testIds.dropdownList.checkIcon)); + expect(secondCheckIcon).toBeDefined(); + + // Close dropdown by clicking outside + await page.click('body', { position: { x: 10, y: 10 } }); + await waitForAnimationEnd(page); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/cancel-feedback-form.ts b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/cancel-feedback-form.ts new file mode 100644 index 00000000..c8aa19bd --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/cancel-feedback-form.ts @@ -0,0 +1,23 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { renderDownvoteResult } from './render-downvote-result'; + +export const cancelFeedbackForm = async (page: Page, skipScreenshots?: boolean): Promise => { + await renderDownvoteResult(page, true); + await waitForAnimationEnd(page); + + const reportButton = page.locator(getSelector(testIds.chatItem.vote.reportButton)); + expect(reportButton).toBeDefined(); + await reportButton.click(); + await waitForAnimationEnd(page); + + const cancelButton = page.locator(getSelector(testIds.feedbackForm.cancelButton)); + expect(cancelButton).toBeDefined(); + await cancelButton.click(); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-downvote-result.ts b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-downvote-result.ts new file mode 100644 index 00000000..7dfc0660 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-downvote-result.ts @@ -0,0 +1,18 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { renderFeedbackForm } from './render-feedback-form'; + +export const renderDownvoteResult = async (page: Page, skipScreenshots?: boolean): Promise => { + await renderFeedbackForm(page, true); + await waitForAnimationEnd(page); + + const thumbsDown = page.locator(getSelector(testIds.chatItem.vote.downvoteLabel)); + expect(thumbsDown).toBeDefined(); + await thumbsDown.click(); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-feedback-form.ts b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-feedback-form.ts new file mode 100644 index 00000000..07212351 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-feedback-form.ts @@ -0,0 +1,46 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const renderFeedbackForm = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + snapToTop: true, + body: 'This message is votable.', + canBeVoted: true, + }); + } + }); + + if (skipScreenshots !== true) { + const thumbsDown = page.locator(getSelector(testIds.chatItem.vote.downvoteLabel)); + expect(thumbsDown).toBeDefined(); + await thumbsDown.click(); + await waitForAnimationEnd(page); + + const reportButton = page.locator(getSelector(testIds.chatItem.vote.reportButton)); + expect(reportButton).toBeDefined(); + await reportButton.click(); + await waitForAnimationEnd(page); + + const commentInput = page.locator(getSelector(testIds.feedbackForm.comment)); + expect(commentInput).toBeDefined(); + await commentInput.fill('This is some feedback comment'); + await waitForAnimationEnd(page); + + const submitButton = page.locator(getSelector(testIds.feedbackForm.submitButton)); + expect(submitButton).toBeDefined(); + + const cancelButton = page.locator(getSelector(testIds.feedbackForm.cancelButton)); + expect(cancelButton).toBeDefined(); + + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-upvote-result.ts b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-upvote-result.ts new file mode 100644 index 00000000..776accd3 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-upvote-result.ts @@ -0,0 +1,18 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { renderFeedbackForm } from './render-feedback-form'; + +export const renderUpvoteResult = async (page: Page, skipScreenshots?: boolean): Promise => { + await renderFeedbackForm(page, true); + await waitForAnimationEnd(page); + + const thumbsUp = page.locator(getSelector(testIds.chatItem.vote.upvoteLabel)); + expect(thumbsUp).toBeDefined(); + await thumbsUp.click(); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-vote-buttons.ts b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-vote-buttons.ts new file mode 100644 index 00000000..f1964b9c --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/render-vote-buttons.ts @@ -0,0 +1,25 @@ +import { expect, Page } from 'playwright/test'; +import { waitForAnimationEnd } from '../../helpers'; + +export const renderVoteButtons = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + snapToTop: true, + body: 'This message is votable.', + canBeVoted: true, + }); + } + }); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/submit-feedback-form.ts b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/submit-feedback-form.ts new file mode 100644 index 00000000..4182f58a --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/feedback-form/submit-feedback-form.ts @@ -0,0 +1,23 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { renderDownvoteResult } from './render-downvote-result'; + +export const submitFeedbackForm = async (page: Page, skipScreenshots?: boolean): Promise => { + await renderDownvoteResult(page, true); + await waitForAnimationEnd(page); + + const reportButton = page.locator(getSelector(testIds.chatItem.vote.reportButton)); + expect(reportButton).toBeDefined(); + await reportButton.click(); + await waitForAnimationEnd(page); + + const submitButton = page.locator(getSelector(testIds.feedbackForm.submitButton)); + expect(submitButton).toBeDefined(); + await submitButton.click(); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/file-pills/file-pills.ts b/vendor/mynah-ui/ui-tests/__test__/flows/file-pills/file-pills.ts new file mode 100644 index 00000000..7c852f9a --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/file-pills/file-pills.ts @@ -0,0 +1,82 @@ +import { Page } from 'playwright'; +import { waitForAnimationEnd } from '../../helpers'; +import { expect } from 'playwright/test'; +import type { ChatItemType } from '@aws/mynah-ui'; + +export const showFilePills = async (page: Page): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { chatItems: [] }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + fullWidth: true, + padding: false, + header: { + icon: 'eye', + body: 'Reading files', + fileList: { + filePaths: [ 'package.json', 'README.md', 'src/app.ts', 'src/components/Button.tsx' ], + details: { + 'package.json': { visibleName: 'package.json', description: 'Package configuration file' }, + 'README.md': { visibleName: 'README.md', description: 'Project documentation' }, + 'src/app.ts': { visibleName: 'app.ts', description: 'Main application file' }, + 'src/components/Button.tsx': { visibleName: 'Button.tsx', description: 'Button component' } + }, + renderAsPills: true + } + } + }); + } + }); + + await waitForAnimationEnd(page); + + const filePillsLocator = page.locator('.mynah-chat-item-tree-file-pill'); + expect(await filePillsLocator.count()).toEqual(4); + + const pillTexts = await filePillsLocator.allTextContents(); + expect(pillTexts).toEqual([ 'package.json', 'README.md', 'app.ts', 'Button.tsx' ]); + + expect(await page.screenshot({ fullPage: true })).toMatchSnapshot('file-pills-basic.png'); +}; + +export const showFilePillsWithDeletedFiles = async (page: Page): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { chatItems: [] }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + fullWidth: true, + padding: false, + header: { + icon: 'check-list', + body: 'Files processed', + fileList: { + filePaths: [ 'src/old-component.ts', 'src/new-component.ts', 'src/updated-file.ts' ], + deletedFiles: [ 'src/old-component.ts' ], + details: { + 'src/old-component.ts': { visibleName: 'old-component.ts', description: 'This file was deleted' }, + 'src/new-component.ts': { visibleName: 'new-component.ts', description: 'This file was created' }, + 'src/updated-file.ts': { visibleName: 'updated-file.ts', description: 'This file was modified' } + }, + renderAsPills: true + } + } + }); + } + }); + + await waitForAnimationEnd(page); + + const filePillsLocator = page.locator('.mynah-chat-item-tree-file-pill'); + expect(await filePillsLocator.count()).toEqual(3); + + const deletedPill = filePillsLocator.filter({ hasText: 'old-component.ts' }); + expect(await deletedPill.getAttribute('class')).toContain('mynah-chat-item-tree-file-pill-deleted'); + + expect(await page.screenshot({ fullPage: true })).toMatchSnapshot('file-pills-with-deleted-files.png'); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/collapse-file-tree.ts b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/collapse-file-tree.ts new file mode 100644 index 00000000..969553e8 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/collapse-file-tree.ts @@ -0,0 +1,42 @@ + +import { Page } from 'playwright'; +import testIds from '../../../../src/helper/test-ids'; +import { showFileTree } from './show-file-tree'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import { expect } from 'playwright/test'; + +export const collapseExpandFileTree = async (page: Page, skipScreenshots?: boolean): Promise => { + await showFileTree(page, true); + + const fileWrapperLocator = page.locator(getSelector(testIds.chatItem.fileTree.wrapper)); + const folderLocator = page.locator(getSelector(testIds.chatItem.fileTree.folder)); + await waitForAnimationEnd(page); + expect(await folderLocator.count()).toEqual(3); + + // Collapse a nested folder + await folderLocator.nth(1).click(); + await page.mouse.move(0, 0); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await fileWrapperLocator.screenshot()).toMatchSnapshot(); + } + + // Collapse the outermost folder + await folderLocator.nth(0).click(); + await page.mouse.move(0, 0); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await fileWrapperLocator.screenshot()).toMatchSnapshot(); + } + + // Expand the outermost folder + await folderLocator.nth(0).click(); + await page.mouse.move(0, 0); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await fileWrapperLocator.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/render-file-details.ts b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/render-file-details.ts new file mode 100644 index 00000000..ea6e8c6a --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/render-file-details.ts @@ -0,0 +1,49 @@ +import { Page } from 'playwright'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { expect } from 'playwright/test'; + +export const renderFileDetails = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, + { + type: 'answer' as any, + fileList: { + rootFolderTitle: 'Folder', + filePaths: [ './package.json', 'src/game.test.ts' ], + deletedFiles: [ + './README.md' + ], + details: { + './package.json': { + icon: 'ok-circled' as any, + description: 'a configuration file', + label: 'configuration added', + status: 'success' + }, + 'src/game.test.ts': { + status: 'error', + icon: 'error' as any, + label: 'tests failed', + } + }, + }, + } + ); + } + }); + await waitForAnimationEnd(page); + + const fileWrapperLocator = page.locator(getSelector(testIds.chatItem.fileTree.wrapper)); + + expect(await fileWrapperLocator.count()).toEqual(1); + if (skipScreenshots !== true) { + expect(await fileWrapperLocator.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/show-file-tooltip.ts b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/show-file-tooltip.ts new file mode 100644 index 00000000..e8a7b0b2 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/show-file-tooltip.ts @@ -0,0 +1,33 @@ + +import { Page } from 'playwright'; +import testIds from '../../../../src/helper/test-ids'; +import { showFileTree } from './show-file-tree'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import { expect } from 'playwright/test'; + +export const showFileTooltip = async (page: Page, skipScreenshots?: boolean): Promise => { + await showFileTree(page, true); + + const fileLocator = page.locator(getSelector(testIds.chatItem.fileTree.file)); + await waitForAnimationEnd(page); + expect(await fileLocator.count()).toEqual(4); + + // Hover over a file to show description + await fileLocator.first().hover(); + const tooltipLocator = page.locator(getSelector(testIds.chatItem.fileTree.fileTooltipWrapper)); + await waitForAnimationEnd(page); + + expect(await tooltipLocator.count()).toEqual(1); + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + // Stop hovering over a file to hide description + await page.mouse.move(0, 0); + await waitForAnimationEnd(page); + + expect(await tooltipLocator.count()).toEqual(0); + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/show-file-tree.ts b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/show-file-tree.ts new file mode 100644 index 00000000..4fb13d14 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/show-file-tree.ts @@ -0,0 +1,55 @@ +import { Page } from 'playwright'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import { expect } from 'playwright/test'; + +export const showFileTree = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, + { + type: 'answer' as any, + fileList: { + rootFolderTitle: 'Folder', + filePaths: [ './package.json', './tsconfig.json', 'src/game.ts', 'tests/game.test.ts' ], + deletedFiles: [], + details: { + './package.json': { + description: 'a configuration file', + }, + }, + actions: { + './package.json': [ + { + icon: 'cancel-circle' as any, + status: 'error', + name: 'reject-change', + description: 'Reject change', + }, + ], + './tsconfig.json': [ + { + icon: 'cancel-circle' as any, + status: 'error', + name: 'reject-change', + description: 'Reject change', + }, + ] + }, + }, + } + ); + } + }); + await waitForAnimationEnd(page); + const fileWrapperLocator = page.locator(getSelector(testIds.chatItem.fileTree.wrapper)); + expect(await fileWrapperLocator.count()).toEqual(1); + if (skipScreenshots !== true) { + expect(await fileWrapperLocator.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/trigger-file-action.ts b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/trigger-file-action.ts new file mode 100644 index 00000000..a132146a --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/file-tree/trigger-file-action.ts @@ -0,0 +1,40 @@ + +import { Page } from 'playwright'; +import testIds from '../../../../src/helper/test-ids'; +import { showFileTree } from './show-file-tree'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import { expect } from 'playwright/test'; + +export const triggerFileActions = async (page: Page, skipScreenshots?: boolean): Promise => { + await showFileTree(page, true); + + // Click on a file to trigger default action + const fileLocator = page.locator(getSelector(testIds.chatItem.fileTree.file)); + expect(await fileLocator.count()).toEqual(4); + await fileLocator.nth(1).click(); + await page.mouse.move(0, 0); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + // Hover over a file to show sub actions + await fileLocator.nth(1).hover(); + const fileActionLocator = page.locator(getSelector(testIds.chatItem.fileTree.fileAction)); + expect(await fileActionLocator.count()).toEqual(2); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + // Click on a file action button to trigger sub action + await fileActionLocator.nth(1).click(); + await page.mouse.move(0, 0); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/form/disable-form.ts b/vendor/mynah-ui/ui-tests/__test__/flows/form/disable-form.ts new file mode 100644 index 00000000..c0225c36 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/form/disable-form.ts @@ -0,0 +1,53 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const disableForm = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + snapToTop: true, + body: 'Can you help us to improve our AI Assistant? Please fill the form below and hit **Submit** to send your feedback.', + formItems: [ + { + id: 'email', + type: 'email', + title: 'Email', + placeholder: 'email', + }, + ], + buttons: [ + { + id: 'submit', + text: 'Submit', + status: 'primary', + }, + { + id: 'cancel-feedback', + text: 'Cancel', + keepCardAfterClick: false, + waitMandatoryFormItems: false, + }, + ], + }); + } + }); + await waitForAnimationEnd(page); + + // Click the submit button + const submitButton = page.locator(getSelector(testIds.chatItem.buttons.button)).nth(0); + expect(submitButton).toBeDefined(); + await submitButton.click(); + + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/form/remove-form.ts b/vendor/mynah-ui/ui-tests/__test__/flows/form/remove-form.ts new file mode 100644 index 00000000..f5973aca --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/form/remove-form.ts @@ -0,0 +1,53 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const removeForm = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + snapToTop: true, + body: 'Can you help us to improve our AI Assistant? Please fill the form below and hit **Submit** to send your feedback.', + formItems: [ + { + id: 'email', + type: 'email', + title: 'Email', + placeholder: 'email', + }, + ], + buttons: [ + { + id: 'submit', + text: 'Submit', + status: 'primary', + }, + { + id: 'cancel-feedback', + text: 'Cancel', + keepCardAfterClick: false, + waitMandatoryFormItems: false, + }, + ], + }); + } + }); + await waitForAnimationEnd(page); + + // Click the cancel button + const cancelButton = page.locator(getSelector(testIds.chatItem.buttons.button)).nth(1); + expect(cancelButton).toBeDefined(); + await cancelButton.click(); + + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/form/render-form-elements.ts b/vendor/mynah-ui/ui-tests/__test__/flows/form/render-form-elements.ts new file mode 100644 index 00000000..cc87cc2f --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/form/render-form-elements.ts @@ -0,0 +1,165 @@ +import { expect, Page } from 'playwright/test'; +import { waitForAnimationEnd } from '../../helpers'; + +export const renderFormElements = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + snapToTop: true, + body: `Can you help us to improve our AI Assistant? Please fill the form below and hit **Submit** to send your feedback. + +_To send the form, mandatory items should be filled._`, + formItems: [ + { + id: 'expertise-area', + type: 'select', + title: 'Area of expertise', + options: [ + { + label: 'Frontend', + value: 'frontend', + }, + { + label: 'Backend', + value: 'backend', + }, + { + label: 'Data Science', + value: 'datascience', + }, + { + label: 'Other', + value: 'other', + }, + ], + }, + { + id: 'preferred-ide', + type: 'radiogroup', + title: 'Preferred IDE', + options: [ + { + label: 'VSCode', + value: 'vscode', + }, + { + label: 'JetBrains IntelliJ', + value: 'intellij', + }, + { + label: 'Visual Studio', + value: 'visualstudio', + }, + ], + }, + { + id: 'remote-ide', + type: 'toggle', + value: 'remote', + title: 'Environment', + options: [ + { + label: 'Remote', + value: 'remote', + icon: 'star' + }, + { + label: 'Local', + value: 'local', + icon: 'scroll-down' + }, + { + label: 'Both', + value: 'both', + icon: 'stack' + } + ], + }, + { + id: 'is-online', + type: 'checkbox', + value: 'true', + label: 'Yes', + title: 'Are you working online?', + }, + { + id: 'is-monorepo', + type: 'switch', + label: 'Yes', + icon: 'deploy', + title: 'Are you working in a monorepo project?', + tooltip: "If you're working more on monorepos, check this" + }, + { + id: 'working-hours', + type: 'numericinput', + title: 'How many hours are you using an IDE weekly?', + placeholder: 'IDE working hours', + }, + { + id: 'email', + type: 'email', + mandatory: true, + title: 'Email', + placeholder: 'email', + }, + { + id: 'name', + type: 'textinput', + mandatory: true, + title: 'Name', + placeholder: 'Name and Surname', + }, + { + id: 'ease-of-usage-rating', + type: 'stars', + mandatory: true, + title: 'How easy is it to use our AI assistant?', + }, + { + id: 'accuracy-rating', + type: 'stars', + mandatory: true, + title: 'How accurate are the answers you get from our AI assistant?', + }, + { + id: 'general-rating', + type: 'stars', + title: 'How do feel about our AI assistant in general?', + }, + { + id: 'description', + type: 'textarea', + title: 'Any other things you would like to share?', + placeholder: 'Write your feelings about our tool', + }, + ], + buttons: [ + { + id: 'submit', + text: 'Submit', + status: 'primary', + }, + { + id: 'cancel-feedback', + text: 'Cancel', + keepCardAfterClick: false, + waitMandatoryFormItems: false, + }, + ], + }); + } + }); + await waitForAnimationEnd(page); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/headers.ts b/vendor/mynah-ui/ui-tests/__test__/flows/headers.ts new file mode 100644 index 00000000..381c520c --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/headers.ts @@ -0,0 +1,168 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; +import { ChatItemType } from '../../../dist/main'; + +export const renderHeaders = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate((body) => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + fullWidth: true, + padding: false, + header: { + icon: 'code-block', + status: { + icon: 'ok', + text: 'Accepted', + status: 'success', + }, + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: [ 'package.json' ], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + fullWidth: true, + padding: false, + header: { + icon: 'code-block', + buttons: [ + { + icon: 'cancel', + status: 'clear', + id: 'reject-file-change-on-header-card', + }, + { + icon: 'ok', + status: 'clear', + id: 'accept-file-change-on-header-card', + }, + ], + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: [ 'package.json' ], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + body: ` +\`\`\`diff-typescript +const mynahUI = new MynahUI({ +tabs: { + 'tab-1': { + isSelected: true, + store: { + tabTitle: 'Chat', + chatItems: [ + { + type: ChatItemType.ANSWER, + body: 'Welcome to our chat!', + messageId: 'welcome-message' + }, + ], +- promptInputPlaceholder: 'Write your question', ++ promptInputPlaceholder: 'Type your question', + } + } +}, +- onChatPrompt: () => {}, ++ onChatPrompt: (tabId: string, prompt: ChatPrompt) => { ++ mynahUI.addChatItem(tabId, { ++ type: ChatItemType.PROMPT, ++ messageId: new Date().getTime().toString(), ++ body: prompt.escapedPrompt ++ }); ++ // call your genAI action ++ } +}); +\`\`\` + `, + codeBlockActions: { + copy: null, + 'insert-to-cursor': null, + }, + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + fullWidth: true, + padding: false, + header: { + icon: 'code-block', + body: 'Terminal command', + status: { + icon: 'warning', + status: 'warning', + description: 'This command may cause\nsignificant data loss or damage.', + }, + buttons: [ + { + status: 'clear', + icon: 'play', + text: 'Run', + id: 'run-bash-command', + }, + ], + }, + body: ` +\`\`\`bash +mkdir -p src/ lalalaaaa +\`\`\` +`, + codeBlockActions: { copy: null, 'insert-to-cursor': null }, + }); + } + }); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const locator1 = page.locator(answerCardSelector).nth(0); + await locator1.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator1.screenshot()).toMatchSnapshot(); + } + + const locator2 = page.locator(answerCardSelector).nth(2); + await locator2.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator2.screenshot()).toMatchSnapshot(); + } + + const locator3 = page.locator(answerCardSelector).nth(4); + await locator3.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator3.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/icons.ts b/vendor/mynah-ui/ui-tests/__test__/flows/icons.ts new file mode 100644 index 00000000..5db53bec --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/icons.ts @@ -0,0 +1,59 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; +import { ChatItemType } from '../../../dist/static'; + +export const renderIcons = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate((body) => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + fullWidth: true, + padding: false, + header: { + body: 'Custom icon + colored (foreground)', + icon: { + name: 'javascript', + base64Svg: + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTIiIGhlaWdodD0iOCIgdmlld0JveD0iMCAwIDEyIDgiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0zLjEyIDAuMjRINC44MlY1QzQuODIgNi4wMjY2NyA0LjU4IDYuNzczMzMgNC4xIDcuMjRDMy42NiA3LjY2NjY3IDMgNy44OCAyLjEyIDcuODhDMS45MiA3Ljg4IDEuNyA3Ljg2NjY3IDEuNDYgNy44NEMxLjIyIDcuOCAxLjAyNjY3IDcuNzQ2NjcgMC44OCA3LjY4TDEuMDYgNi4zMkMxLjMxMzMzIDYuNDQgMS42MDY2NyA2LjUgMS45NCA2LjVDMi4zMTMzMyA2LjUgMi41OTMzMyA2LjQgMi43OCA2LjJDMy4wMDY2NyA1Ljk2IDMuMTIgNS41NiAzLjEyIDVWMC4yNFpNNi4zMiA2QzYuNTYgNi4xMzMzMyA2Ljg0IDYuMjQ2NjcgNy4xNiA2LjM0QzcuNTIgNi40NDY2NyA3Ljg2IDYuNSA4LjE4IDYuNUM4LjU4IDYuNSA4Ljg4IDYuNDMzMzMgOS4wOCA2LjNDOS4yOCA2LjE1MzMzIDkuMzggNS45NTMzMyA5LjM4IDUuN0M5LjM4IDUuNDQ2NjcgOS4yODY2NyA1LjI0NjY3IDkuMSA1LjFDOC45MTMzMyA0Ljk0IDguNTg2NjcgNC43OCA4LjEyIDQuNjJDNi43NDY2NyA0LjE0IDYuMDYgMy4zOTMzMyA2LjA2IDIuMzhDNi4wNiAxLjcxMzMzIDYuMzEzMzMgMS4xNzMzMyA2LjgyIDAuNzU5OTk5QzcuMzQgMC4zMzMzMzMgOC4wNDY2NyAwLjEyIDguOTQgMC4xMkM5LjY0NjY3IDAuMTIgMTAuMjkzMyAwLjI0NjY2NyAxMC44OCAwLjVMMTAuNSAxLjg4TDEwLjM4IDEuODJDMTAuMTQgMS43MjY2NyA5Ljk0NjY3IDEuNjYgOS44IDEuNjJDOS41MiAxLjU0IDkuMjMzMzMgMS41IDguOTQgMS41QzguNTggMS41IDguMyAxLjU3MzMzIDguMSAxLjcyQzcuOTEzMzMgMS44NTMzMyA3LjgyIDIuMDMzMzMgNy44MiAyLjI2QzcuODIgMi40ODY2NyA3LjkyNjY3IDIuNjczMzMgOC4xNCAyLjgyQzguMyAyLjk0IDguNjQ2NjcgMy4xMDY2NyA5LjE4IDMuMzJDOS44NDY2NyAzLjU3MzMzIDEwLjMzMzMgMy44OCAxMC42NCA0LjI0QzEwLjk2IDQuNiAxMS4xMiA1LjA0IDExLjEyIDUuNTZDMTEuMTIgNi4yMjY2NyAxMC44NjY3IDYuNzY2NjcgMTAuMzYgNy4xOEM5LjgxMzMzIDcuNjQ2NjcgOS4wNDY2NyA3Ljg4IDguMDYgNy44OEM3LjY3MzMzIDcuODggNy4yNjY2NyA3LjgyNjY3IDYuODQgNy43MkM2LjUwNjY3IDcuNjUzMzMgNi4yMDY2NyA3LjU2IDUuOTQgNy40NEw2LjMyIDZaIiBmaWxsPSIjQ0JDQjQxIi8+Cjwvc3ZnPgo=', + }, + iconForegroundStatus: 'success' + }, + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + fullWidth: true, + padding: false, + header: { + body: 'Custom icon + colored (background)', + icon: { + name: 'typescript', + base64Svg: + 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPgo8c3ZnIHdpZHRoPSI4MDBweCIgaGVpZ2h0PSI4MDBweCIgdmlld0JveD0iMCAwIDMyIDMyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjx0aXRsZT5maWxlX3R5cGVfdHlwZXNjcmlwdDwvdGl0bGU+PHBhdGggZD0iTTIzLjgyNyw4LjI0M0E0LjQyNCw0LjQyNCwwLDAsMSwyNi4wNSw5LjUyNGE1Ljg1Myw1Ljg1MywwLDAsMSwuODUyLDEuMTQzYy4wMTEuMDQ1LTEuNTM0LDEuMDgzLTIuNDcxLDEuNjYyLS4wMzQuMDIzLS4xNjktLjEyNC0uMzIyLS4zNWEyLjAxNCwyLjAxNCwwLDAsMC0xLjY3LTFjLTEuMDc3LS4wNzQtMS43NzEuNDktMS43NjYsMS40MzNhMS4zLDEuMywwLDAsMCwuMTUzLjY2NmMuMjM3LjQ5LjY3Ny43ODQsMi4wNTksMS4zODMsMi41NDQsMS4wOTUsMy42MzYsMS44MTcsNC4zMSwyLjg0M2E1LjE1OCw1LjE1OCwwLDAsMSwuNDE2LDQuMzMzLDQuNzY0LDQuNzY0LDAsMCwxLTMuOTMyLDIuODE1LDEwLjksMTAuOSwwLDAsMS0yLjcwOC0uMDI4LDYuNTMxLDYuNTMxLDAsMCwxLTMuNjE2LTEuODg0LDYuMjc4LDYuMjc4LDAsMCwxLS45MjYtMS4zNzEsMi42NTUsMi42NTUsMCwwLDEsLjMyNy0uMjA4Yy4xNTgtLjA5Ljc1Ni0uNDM0LDEuMzItLjc2MUwxOS4xLDE5LjZsLjIxNC4zMTJhNC43NzEsNC43NzEsMCwwLDAsMS4zNSwxLjI5MiwzLjMsMy4zLDAsMCwwLDMuNDU4LS4xNzUsMS41NDUsMS41NDUsMCwwLDAsLjItMS45NzRjLS4yNzYtLjM5NS0uODQtLjcyNy0yLjQ0My0xLjQyMmE4LjgsOC44LDAsMCwxLTMuMzQ5LTIuMDU1LDQuNjg3LDQuNjg3LDAsMCwxLS45NzYtMS43NzcsNy4xMTYsNy4xMTYsMCwwLDEtLjA2Mi0yLjI2OCw0LjMzMiw0LjMzMiwwLDAsMSwzLjY0NC0zLjM3NEE5LDksMCwwLDEsMjMuODI3LDguMjQzWk0xNS40ODQsOS43MjZsLjAxMSwxLjQ1NGgtNC42M1YyNC4zMjhINy42VjExLjE4M0gyLjk3VjkuNzU1QTEzLjk4NiwxMy45ODYsMCwwLDEsMy4wMSw4LjI4OWMuMDE3LS4wMjMsMi44MzItLjAzNCw2LjI0NS0uMDI4bDYuMjExLjAxN1oiIHN0eWxlPSJmaWxsOiMwMDdhY2MiLz48L3N2Zz4=', + }, + iconStatus: 'success' + }, + }); + } + }); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const locator1 = page.locator(answerCardSelector).nth(0); + await locator1.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator1.screenshot()).toMatchSnapshot(); + } + + const locator2 = page.locator(answerCardSelector).nth(2); + await locator2.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator2.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/init-render.ts b/vendor/mynah-ui/ui-tests/__test__/flows/init-render.ts new file mode 100644 index 00000000..567f4f0e --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/init-render.ts @@ -0,0 +1,13 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const initRender = async (page: Page): Promise => { + const welcomeCardSelector = `${getSelector(testIds.chatItem.type.answer)}[messageid="welcome-message"]`; + const welcomeCard = await page.waitForSelector(welcomeCardSelector); + await waitForAnimationEnd(page); + + expect(welcomeCard).toBeDefined(); + + expect(await page.screenshot()).toMatchSnapshot(); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/link-hover-preview.ts b/vendor/mynah-ui/ui-tests/__test__/flows/link-hover-preview.ts new file mode 100644 index 00000000..5c8782d8 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/link-hover-preview.ts @@ -0,0 +1,60 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const hoverOverLink = async (page: Page, skipScreenshots?: boolean): Promise => { + const mockSource = + { + url: 'https://github.com/aws/mynah-ui', + title: 'Mock Source 1', + body: `## Perque adacto fugio + +Invectae moribundo et eripiet sine, adventu tolli *liquidas* satiatur Perseus; +**locus**, nato! More dei timeas dextra Granico neu corpus simul *operique*! +Fecit mea, sua, hoc vias proles pallebant illa est populosque festa manetque +clamato nescisse.`, + }; + + await page.evaluate((source) => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + body: 'Text', + relatedContent: { + content: [ source ], + title: 'Sources', + }, + }); + } + }, mockSource); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + const linkWrapperLocator = page.locator(getSelector(testIds.chatItem.relatedLinks.linkWrapper)); + await linkWrapperLocator.dispatchEvent('mouseenter', { bubbles: true, cancelable: true }); + + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + expect(await page.locator(getSelector(testIds.chatItem.relatedLinks.linkPreviewOverlay)).count()).toEqual(1); + expect(await page.locator(getSelector(testIds.chatItem.relatedLinks.linkPreviewOverlayCard)).count()).toEqual(1); + + await linkWrapperLocator.first().dispatchEvent('mouseleave'); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + expect(await page.locator(getSelector(testIds.chatItem.relatedLinks.linkPreviewOverlay)).count()).toEqual(0); + expect(await page.locator(getSelector(testIds.chatItem.relatedLinks.linkPreviewOverlayCard)).count()).toEqual(0); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/markdown-parser/all-markdown-tags.ts b/vendor/mynah-ui/ui-tests/__test__/flows/markdown-parser/all-markdown-tags.ts new file mode 100644 index 00000000..d80ebba8 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/markdown-parser/all-markdown-tags.ts @@ -0,0 +1,73 @@ +const img = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI2MDAiIGhlaWdodD0iNDAwIiB2aWV3Qm94PSIwIDAgNjAwIDQwMCI+PHJlY3Qgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0iI0RERERERCIvPjxwYXRoIGZpbGw9IiM5OTk5OTkiIGQ9Im0xNTUuNTIgMTcxLjA4LTE3LjE2IDIxLTIgMi40OHExLjY4LS44NCAzLjYyLTEuMyAxLjk0LS40NiA0LjE4LS40NiAzLjI4IDAgNi4zOCAxLjA4dDUuNDYgMy4yNnEyLjM2IDIuMTggMy44IDUuNDIgMS40NCAzLjI0IDEuNDQgNy41NiAwIDQuMDQtMS40OCA3LjU4dC00LjE2IDYuMThxLTIuNjggMi42NC02LjQ2IDQuMTZ0LTguMzQgMS41MnEtNC42NCAwLTguMzItMS40OC0zLjY4LTEuNDgtNi4yOC00LjE0LTIuNi0yLjY2LTMuOTgtNi40dC0xLjM4LTguM3EwLTQuMDggMS42Ni04LjM4IDEuNjYtNC4zIDUuMTQtOC45NGwxMy44LTE4LjUycS43Mi0uOTYgMi4xLTEuNjQgMS4zOC0uNjggMy4xOC0uNjhoOC44Wm0tMTQuOTIgNTAuNHEyLjM2IDAgNC4zNC0uOHQzLjQtMi4yNHExLjQyLTEuNDQgMi4yMi0zLjM4LjgtMS45NC44LTQuMjIgMC0yLjQ4LS43Ni00LjQ2dC0yLjE2LTMuMzZxLTEuNC0xLjM4LTMuMzYtMi4xLTEuOTYtLjcyLTQuMzItLjcyLTIuMzYgMC00LjI4LjgtMS45Mi44LTMuMjggMi4yMi0xLjM2IDEuNDItMi4xMiAzLjM2dC0uNzYgNC4xOHEwIDIuNC42NiA0LjM4dDEuOTYgMy4zOHExLjMgMS40IDMuMjIgMi4xOCAxLjkyLjc4IDQuNDQuNzhaTTIwOC4yOCAyMDBxMCA3LjU2LTEuNjIgMTMuMTQtMS42MiA1LjU4LTQuNDggOS4yMi0yLjg2IDMuNjQtNi43NiA1LjQyLTMuOSAxLjc4LTguNDIgMS43OHQtOC4zOC0xLjc4cS0zLjg2LTEuNzgtNi43LTUuNDItMi44NC0zLjY0LTQuNDQtOS4yMi0xLjYtNS41OC0xLjYtMTMuMTQgMC03LjYgMS42LTEzLjE2IDEuNi01LjU2IDQuNDQtOS4yIDIuODQtMy42NCA2LjctNS40MiAzLjg2LTEuNzggOC4zOC0xLjc4IDQuNTIgMCA4LjQyIDEuNzggMy45IDEuNzggNi43NiA1LjQyIDIuODYgMy42NCA0LjQ4IDkuMiAxLjYyIDUuNTYgMS42MiAxMy4xNlptLTEwLjIgMHEwLTYuMjgtLjkyLTEwLjR0LTIuNDYtNi41NnEtMS41NC0yLjQ0LTMuNTQtMy40MnQtNC4xNi0uOThxLTIuMTIgMC00LjEuOTgtMS45OC45OC0zLjUgMy40MnQtMi40MiA2LjU2cS0uOSA0LjEyLS45IDEwLjR0LjkgMTAuNHEuOSA0LjEyIDIuNDIgNi41NiAxLjUyIDIuNDQgMy41IDMuNDJ0NC4xLjk4cTIuMTYgMCA0LjE2LS45OHQzLjU0LTMuNDJxMS41NC0yLjQ0IDIuNDYtNi41Ni45Mi00LjEyLjkyLTEwLjRabTU2LjYgMHEwIDcuNTYtMS42MiAxMy4xNC0xLjYyIDUuNTgtNC40OCA5LjIyLTIuODYgMy42NC02Ljc2IDUuNDItMy45IDEuNzgtOC40MiAxLjc4dC04LjM4LTEuNzhxLTMuODYtMS43OC02LjctNS40Mi0yLjg0LTMuNjQtNC40NC05LjIyLTEuNi01LjU4LTEuNi0xMy4xNCAwLTcuNiAxLjYtMTMuMTYgMS42LTUuNTYgNC40NC05LjIgMi44NC0zLjY0IDYuNy01LjQyIDMuODYtMS43OCA4LjM4LTEuNzggNC41MiAwIDguNDIgMS43OCAzLjkgMS43OCA2Ljc2IDUuNDIgMi44NiAzLjY0IDQuNDggOS4yIDEuNjIgNS41NiAxLjYyIDEzLjE2Wm0tMTAuMiAwcTAtNi4yOC0uOTItMTAuNHQtMi40Ni02LjU2cS0xLjU0LTIuNDQtMy41NC0zLjQydC00LjE2LS45OHEtMi4xMiAwLTQuMS45OC0xLjk4Ljk4LTMuNSAzLjQydC0yLjQyIDYuNTZxLS45IDQuMTItLjkgMTAuNHQuOSAxMC40cS45IDQuMTIgMi40MiA2LjU2IDEuNTIgMi40NCAzLjUgMy40MnQ0LjEuOThxMi4xNiAwIDQuMTYtLjk4dDMuNTQtMy40MnExLjU0LTIuNDQgMi40Ni02LjU2LjkyLTQuMTIuOTItMTAuNFptNzMuNzIgMTUuNjgtNS4yNCA1LjE2LTEzLjU2LTEzLjU2LTEzLjY4IDEzLjY0LTUuMjQtNS4xNiAxMy42OC0xMy43MkwyODEuMTIgMTg5bDUuMi01LjIgMTMuMDQgMTMuMDQgMTIuOTYtMTIuOTYgNS4yOCA1LjItMTMgMTMgMTMuNiAxMy42Wm0zNC42NC04LjU2aDE3LjZWMTg4LjJxMC0yLjY4LjM2LTUuOTJsLTE3Ljk2IDI0Ljg0Wm0yNi4yIDBoNy4yOHY1LjcycTAgLjgtLjUyIDEuMzgtLjUyLjU4LTEuNDguNThoLTUuMjh2MTQuMTJoLTguNlYyMTQuOGgtMjQuNHEtMSAwLTEuNzYtLjYydC0uOTYtMS41NGwtMS4wNC01IDI3LjQtMzYuNmg5LjM2djM2LjA4Wm01My43Mi03LjEycTAgNy41Ni0xLjYyIDEzLjE0LTEuNjIgNS41OC00LjQ4IDkuMjItMi44NiAzLjY0LTYuNzYgNS40Mi0zLjkgMS43OC04LjQyIDEuNzh0LTguMzgtMS43OHEtMy44Ni0xLjc4LTYuNy01LjQyLTIuODQtMy42NC00LjQ0LTkuMjItMS42LTUuNTgtMS42LTEzLjE0IDAtNy42IDEuNi0xMy4xNiAxLjYtNS41NiA0LjQ0LTkuMiAyLjg0LTMuNjQgNi43LTUuNDIgMy44Ni0xLjc4IDguMzgtMS43OCA0LjUyIDAgOC40MiAxLjc4IDMuOSAxLjc4IDYuNzYgNS40MiAyLjg2IDMuNjQgNC40OCA5LjIgMS42MiA1LjU2IDEuNjIgMTMuMTZabS0xMC4yIDBxMC02LjI4LS45Mi0xMC40dC0yLjQ2LTYuNTZxLTEuNTQtMi40NC0zLjU0LTMuNDJ0LTQuMTYtLjk4cS0yLjEyIDAtNC4xLjk4LTEuOTguOTgtMy41IDMuNDJ0LTIuNDIgNi41NnEtLjkgNC4xMi0uOSAxMC40dC45IDEwLjRxLjkgNC4xMiAyLjQyIDYuNTYgMS41MiAyLjQ0IDMuNSAzLjQydDQuMS45OHEyLjE2IDAgNC4xNi0uOTh0My41NC0zLjQycTEuNTQtMi40NCAyLjQ2LTYuNTYuOTItNC4xMi45Mi0xMC40Wm01Ni42IDBxMCA3LjU2LTEuNjIgMTMuMTQtMS42MiA1LjU4LTQuNDggOS4yMi0yLjg2IDMuNjQtNi43NiA1LjQyLTMuOSAxLjc4LTguNDIgMS43OHQtOC4zOC0xLjc4cS0zLjg2LTEuNzgtNi43LTUuNDItMi44NC0zLjY0LTQuNDQtOS4yMi0xLjYtNS41OC0xLjYtMTMuMTQgMC03LjYgMS42LTEzLjE2IDEuNi01LjU2IDQuNDQtOS4yIDIuODQtMy42NCA2LjctNS40MiAzLjg2LTEuNzggOC4zOC0xLjc4IDQuNTIgMCA4LjQyIDEuNzggMy45IDEuNzggNi43NiA1LjQyIDIuODYgMy42NCA0LjQ4IDkuMiAxLjYyIDUuNTYgMS42MiAxMy4xNlptLTEwLjIgMHEwLTYuMjgtLjkyLTEwLjR0LTIuNDYtNi41NnEtMS41NC0yLjQ0LTMuNTQtMy40MnQtNC4xNi0uOThxLTIuMTIgMC00LjEuOTgtMS45OC45OC0zLjUgMy40MnQtMi40MiA2LjU2cS0uOSA0LjEyLS45IDEwLjR0LjkgMTAuNHEuOSA0LjEyIDIuNDIgNi41NiAxLjUyIDIuNDQgMy41IDMuNDJ0NC4xLjk4cTIuMTYgMCA0LjE2LS45OHQzLjU0LTMuNDJxMS41NC0yLjQ0IDIuNDYtNi41Ni45Mi00LjEyLjkyLTEwLjRaIi8+PC9zdmc+'; +export default `# Heading 1 +## Heading 2 +### Heading 3 +#### Heading 4 +##### Heading 5 +###### Heading 6 + + +*Italic text* +_Italic text_ + +**Bold text** +__Bold text__ + +***Bold and italic text*** +___Bold and italic text___ + + +- Item 1 +- Item 2 + - Subitem 1 + - Subitem 2 + + +1. [x] Task 1 +2. [ ] Task 2 +3. [ ] Task 3 + + +[mynah-ui](https://github.com/aws/mynah-ui) + + + + +![Alt text](${img}) + + +> This is a blockquote. + + +\`inline code\`. + +\`\`\` +no syntax declared +\`\`\` +  +\`\`\`javascript +const x; +\`\`\` + +| left | center | right | +| :----- | :-----: | -----: | +| 1 | 1 | 1 | +| 2 | 2 | 2 | + + +--- + +*** + +___ + + +~~Strikethrough text~~ + +

    This is an HTML paragraph.

    + +
    +Summary +Detail +
    +`; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/markdown-parser/markdown-parser.ts b/vendor/mynah-ui/ui-tests/__test__/flows/markdown-parser/markdown-parser.ts new file mode 100644 index 00000000..41cfb262 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/markdown-parser/markdown-parser.ts @@ -0,0 +1,100 @@ +import { expect, Page } from 'playwright/test'; +import { DEFAULT_VIEWPORT, getOffsetHeight, getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; +import allMarkdown from './all-markdown-tags'; + +export const parseMarkdown = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate((body) => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + snapToTop: true, + codeReference: [ { + information: 'Hello Reference Tracker', + recommendationContentSpan: { + start: 4828, // because of base64 image length calculation + end: 4837 + } + } ], + body + }); + } + }, allMarkdown); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const answerCard = await page.waitForSelector(answerCardSelector); + const newViewportHeight = getOffsetHeight(await answerCard.boundingBox()) ?? 1000; + + // Update viewport size + await page.setViewportSize({ + width: DEFAULT_VIEWPORT.width, + // Chromium doesn't accept float numbers for the viewport, has to be converted to int + height: Math.ceil(newViewportHeight) + 300 + }); + await answerCard.scrollIntoViewIfNeeded(); + + // Headings + expect(await answerCard.evaluate(node => node.querySelector('h1')?.innerText)).toBe('Heading 1'); + expect(await answerCard.evaluate(node => node.querySelector('h2')?.innerText)).toBe('Heading 2'); + expect(await answerCard.evaluate(node => node.querySelector('h3')?.innerText)).toBe('Heading 3'); + expect(await answerCard.evaluate(node => node.querySelector('h4')?.innerText)).toBe('Heading 4'); + expect(await answerCard.evaluate(node => node.querySelector('h5')?.innerText)).toBe('Heading 5'); + expect(await answerCard.evaluate(node => node.querySelector('h6')?.innerText)).toBe('Heading 6'); + + // Italic, bold and strikethrough + expect(await answerCard.evaluate(node => node.querySelector('del')?.innerText)).toBe('Strikethrough text'); + expect(await answerCard.evaluate(node => Array.from(node.querySelectorAll('em')).filter(elm => elm.innerText === 'Italic text').length)).toBe(2); + expect(await answerCard.evaluate(node => Array.from(node.querySelectorAll('strong')).filter(elm => elm.innerText === 'Bold text').length)).toBe(2); + expect(await answerCard.evaluate(node => Array.from(node.querySelectorAll('em > strong')).filter((elm) => (elm as HTMLElement).innerText === 'Bold and italic text').length)).toBe(2); + + // List items + expect(await answerCard.evaluate(node => { + const firstUl = node.querySelector('ul'); + const firstLiP = firstUl?.querySelector('li:first-child > p'); + return firstLiP != null ? (firstLiP as HTMLElement).innerText : ''; + })).toBe('Item 1'); + expect(await answerCard.evaluate(node => { + const firstUl = node.querySelector('ul'); + const secondLiUlLi = firstUl?.querySelector('li:nth-child(2) > ul > li:first-child > p'); + return secondLiUlLi != null ? (secondLiUlLi as HTMLElement).innerText : ''; + })).toBe('Subitem 1'); + expect(await answerCard.evaluate(node => node.querySelector('ol > li:first-child > input[checked][disabled][type="checkbox"]'))).toBeDefined(); + + // Anchors and IMG + // We're not expecting any link except [TEXT](URL) format, we should have only 1 link. + expect(await answerCard.evaluate(node => node.querySelectorAll('a[href="https://github.com/aws/mynah-ui"]').length)).toBe(1); + expect(await answerCard.evaluate(node => node.querySelectorAll('a[href="https://github.com/aws/mynah-ui"]')[0]?.innerHTML)).toBe('mynah-ui'); + expect(await answerCard.evaluate(node => node.querySelector('img[src="https://placehold.co/600x400"]'))).toBeDefined(); + + // Table + expect(await answerCard.evaluate(node => node.querySelector('table > tbody > tr:nth-child(2) > td:nth-child(2)')?.innerHTML)).toBe('2'); + + // Code blocks + expect(await answerCard.evaluate(node => node.querySelectorAll('pre > code')[0]?.innerHTML)).toBe('inline code'); + expect(await answerCard.evaluate(node => node.querySelectorAll('pre')[2]?.nextElementSibling?.nextElementSibling?.querySelector(':scope > span')?.innerHTML)).toBe('javascript'); + expect(await answerCard.evaluate(node => node.querySelectorAll('pre')[2]?.nextElementSibling?.nextElementSibling?.querySelector(':scope > button')?.querySelector(':scope > span')?.innerHTML)).toBe('Copy'); + + // Reference highlight + expect(await answerCard.evaluate(node => node.querySelectorAll('pre')[1]?.querySelector(':scope > code > mark')?.innerHTML)).toBe('no syntax'); + + // Reference hover + const markPosition = await answerCard.evaluate(node => node.querySelectorAll('pre')[2]?.querySelector(':scope > code > mark')?.getBoundingClientRect()); + if (markPosition != null) { + await page.mouse.move(Number(markPosition.top) + 2, Number(markPosition.left) + 2); + await waitForAnimationEnd(page); + } + expect(page.getByText('Hello Reference Tracker')).toBeDefined(); + page.mouse.move(0, 0); + await waitForAnimationEnd(page); + expect(await page.getByText('Hello Reference Tracker').isHidden()).toBeTruthy(); + + if (skipScreenshots !== true) { + expect(await answerCard.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/muted-cards.ts b/vendor/mynah-ui/ui-tests/__test__/flows/muted-cards.ts new file mode 100644 index 00000000..9e855253 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/muted-cards.ts @@ -0,0 +1,76 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; +import { ChatItemType } from '../../../dist/main'; + +export const renderMutedCards = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate((body) => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + muted: true, + body: 'This is an extended card with an icon and a different border color. It also includes some action buttons.', + status: 'error', + icon: 'error', + buttons: [ + { + text: 'I Understand', + id: 'understood', + status: 'error', + icon: 'ok', + }, + ], + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as ChatItemType, + messageId: new Date().getTime().toString(), + fullWidth: true, + padding: false, + muted: true, + header: { + icon: 'code-block', + status: { + icon: 'ok', + text: 'Accepted', + status: 'success', + }, + fileList: { + hideFileCount: true, + fileTreeTitle: '', + filePaths: [ 'package.json' ], + details: { + 'package.json': { + icon: null, + label: 'Created', + changes: { + added: 36, + deleted: 0, + total: 36, + }, + }, + }, + }, + }, + }); + } + }); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const locator1 = page.locator(answerCardSelector).nth(0); + await locator1.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator1.screenshot()).toMatchSnapshot(); + } + + const locator2 = page.locator(answerCardSelector).nth(1); + await locator2.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator2.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-back-to-current-prompt-with-code-attachment.ts b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-back-to-current-prompt-with-code-attachment.ts new file mode 100644 index 00000000..0719b477 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-back-to-current-prompt-with-code-attachment.ts @@ -0,0 +1,44 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const navigateBackToCurrentPromptWithCodeAttachment = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.locator(getSelector(testIds.prompt.input)).fill('This is the first user prompt'); + await page.locator(getSelector(testIds.prompt.send)).nth(1).click(); + await waitForAnimationEnd(page); + + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.addToUserPrompt( + selectedTabId, + 'This is an unsent code attachment', + 'code', + ); + } + }); + await waitForAnimationEnd(page); + + const promptInput = page.locator(getSelector(testIds.prompt.input)); + await promptInput.press('ArrowUp'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + // we add .trim() because webpack test was failing otherwise, as it adds a \n at the end, like 'This is an unsent code attachment\n' + const codeAttachmentContent = (await page.locator(getSelector(testIds.prompt.attachmentWrapper)).innerText()).trim(); + expect(codeAttachmentContent).toBe('This is an unsent code attachment'); + + // Move the mouse outside of the attachment + await page.mouse.move(0, 0); + await justWait(500); + + if (skipScreenshots !== true) { + const wrapper = page.locator(getSelector(testIds.prompt.wrapper)); + expect(await wrapper.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-back-to-current-prompt.ts b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-back-to-current-prompt.ts new file mode 100644 index 00000000..ca970b21 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-back-to-current-prompt.ts @@ -0,0 +1,30 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const navigateBackToCurrentPrompt = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.locator(getSelector(testIds.prompt.input)).fill('This is the first user prompt'); + + await page.locator(getSelector(testIds.prompt.send)).nth(1).click(); + await waitForAnimationEnd(page); + + // Write prompt without sending it + await page.locator(getSelector(testIds.prompt.input)).fill('This is the second unsent user prompt'); + await waitForAnimationEnd(page); + + const promptInput = page.locator(getSelector(testIds.prompt.input)); + await promptInput.press('ArrowUp'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + expect(await promptInput.innerText()).toBe('This is the second unsent user prompt'); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-down.ts b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-down.ts new file mode 100644 index 00000000..e612ad81 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-down.ts @@ -0,0 +1,32 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const navigatePromptsDown = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.locator(getSelector(testIds.prompt.input)).fill('This is the first user prompt'); + await page.locator(getSelector(testIds.prompt.send)).nth(1).click(); + await waitForAnimationEnd(page); + + await page.locator(getSelector(testIds.prompt.input)).fill('This is the second user prompt'); + await page.locator(getSelector(testIds.prompt.send)).nth(1).click(); + await waitForAnimationEnd(page); + + const promptInput = page.locator(getSelector(testIds.prompt.input)); + await promptInput.press('ArrowUp'); + await waitForAnimationEnd(page); + + await promptInput.press('ArrowUp'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + expect(await promptInput.innerText()).toBe('This is the second user prompt'); + + if (skipScreenshots !== true) { + expect(await promptInput.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-first-last-line-check.ts b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-first-last-line-check.ts new file mode 100644 index 00000000..3d5750e5 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-first-last-line-check.ts @@ -0,0 +1,46 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const navigatePromptsFirstLastLineCheck = async (page: Page, skipScreenshots?: boolean): Promise => { + const firstPrompt = 'This is the first user prompt'; + const secondPrompt = 'This is the second user prompt.\nIt spans two separate lines.'; + + const promptInput = page.locator(getSelector(testIds.prompt.input)); + const sendButton = page.locator(getSelector(testIds.prompt.send)).nth(1); + + await promptInput.fill(firstPrompt); + await sendButton.click(); + await waitForAnimationEnd(page); + + await promptInput.fill(secondPrompt); + await waitForAnimationEnd(page); + + // The input should start as the input with two lines + expect(await promptInput.innerText()).toBe(secondPrompt); + + // Input should remain the same as it is multiline + await promptInput.press('ArrowDown'); + await justWait(100); + expect(await promptInput.innerText()).toBe(secondPrompt); + + // Go back to the first line + await promptInput.press('ArrowUp'); + // Go to the beginning of the line + await promptInput.press('ArrowUp'); + await justWait(100); + + // Now that we're in the first line again, it should navigate to the first user prompt + await promptInput.press('ArrowUp'); + await justWait(100); + expect(await promptInput.innerText()).toBe(firstPrompt); + + // Given that this input only has one line, we should be able to go down to prompt 2 immediately again, after going to the end of the text + await promptInput.press('ArrowDown'); + await justWait(100); + await promptInput.press('ArrowDown'); + await justWait(100); + + // The explicit \n is lost at the end, so we account for that as it is expected + expect(await promptInput.innerText()).toBe(secondPrompt.replace('\n', '')); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-to-empty.ts b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-to-empty.ts new file mode 100644 index 00000000..9dda5c85 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-to-empty.ts @@ -0,0 +1,25 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const navigatePromptsToEmpty = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.locator(getSelector(testIds.prompt.input)).fill('This is the first user prompt'); + await page.locator(getSelector(testIds.prompt.send)).nth(1).click(); + await waitForAnimationEnd(page); + + const promptInput = page.locator(getSelector(testIds.prompt.input)); + await promptInput.press('ArrowUp'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + await promptInput.press('ArrowDown'); + await justWait(100); + + expect(await promptInput.innerText()).toBe(''); + + if (skipScreenshots !== true) { + expect(await promptInput.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-up.ts b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-up.ts new file mode 100644 index 00000000..ca340555 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/navigate-prompts-up.ts @@ -0,0 +1,20 @@ +// navigatePromptsUp.ts +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const navigatePromptsUp = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.locator(getSelector(testIds.prompt.input)).fill('This is the first user prompt'); + await page.locator(getSelector(testIds.prompt.send)).nth(1).click(); + await waitForAnimationEnd(page); + + const promptInput = page.locator(`${getSelector(testIds.prompt.input)}`); + await promptInput.press('ArrowUp'); + await waitForAnimationEnd(page); + + expect(await promptInput.innerText()).toBe('This is the first user prompt'); + + if (skipScreenshots !== true) { + expect(await promptInput.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/stay-on-current-prompt.ts b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/stay-on-current-prompt.ts new file mode 100644 index 00000000..6e6f316a --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/navigate-prompts/stay-on-current-prompt.ts @@ -0,0 +1,23 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const stayOnCurrentPrompt = async (page: Page, skipScreenshots?: boolean): Promise => { + // Write prompt without sending it + await page.locator(getSelector(testIds.prompt.input)).fill('This is the first unsent user prompt'); + await waitForAnimationEnd(page); + + let promptInput = page.locator(`${getSelector(testIds.prompt.input)}`); + await promptInput.press('ArrowUp'); + await waitForAnimationEnd(page); + + promptInput = page.locator(`${getSelector(testIds.prompt.input)}`); + await promptInput.press('ArrowDown'); + await waitForAnimationEnd(page); + + expect(await promptInput.innerText()).toBe('This is the first unsent user prompt'); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/open-new-tab.ts b/vendor/mynah-ui/ui-tests/__test__/flows/open-new-tab.ts new file mode 100644 index 00000000..8b2d01da --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/open-new-tab.ts @@ -0,0 +1,22 @@ +import { Page } from 'playwright'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; +import { expect } from 'playwright/test'; + +export const openNewTab = async (page: Page, withMiddleClick?: boolean, skipScreenshots?: boolean): Promise => { + // Open new tab + if (withMiddleClick !== true) { + await page.locator(getSelector(testIds.tabBar.tabAddButton)).click(); + } else { + await page.mouse.move(100, 10); + await page.locator(getSelector(testIds.tabBar.wrapper)).dblclick({ position: { x: 100, y: 10 } }); + } + await page.mouse.move(0, 0); + + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + // snap + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/prompt-options.ts b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-options.ts new file mode 100644 index 00000000..185b5328 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-options.ts @@ -0,0 +1,52 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const promptOptions = async (page: Page): Promise => { + const promptOptionsSelector = getSelector(testIds.prompt.options); + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptInputOptions: [ + { + type: 'toggle', + id: 'prompt-type', + value: 'ask', + options: [ { + value: 'ask', + icon: 'chat' + }, { + value: 'do', + icon: 'flash' + } ] + } + ], + promptInputButtons: [ + { + id: 'upgrade-q', + icon: 'bug', + } + ] + }); + } + }); + const promptOptionsWrapper = (await page.waitForSelector(`${promptOptionsSelector}`)); + expect(await promptOptionsWrapper.isVisible()).toBeTruthy(); + await waitForAnimationEnd(page); + + // snap + expect(await page.screenshot()).toMatchSnapshot(); + + // Remove options + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptInputOptions: [], + promptInputButtons: [] + }); + } + }); + expect(await promptOptionsWrapper.isVisible()).toBeFalsy(); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/prompt-progress-indicator.ts b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-progress-indicator.ts new file mode 100644 index 00000000..dfb5a3ab --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-progress-indicator.ts @@ -0,0 +1,38 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const progressIndicator = async (page: Page): Promise => { + const progressSelector = getSelector(testIds.prompt.progress); + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptInputProgress: { + value: 10, + text: 'Progress', + valueText: '10%', + status: 'info' + } + }); + } + }); + const progressWrapper = await page.waitForSelector(`${progressSelector}:not(.no-content)`); + expect(progressWrapper).toBeDefined(); + await waitForAnimationEnd(page); + + // snap + expect(await page.screenshot()).toMatchSnapshot(); + + // Remove the progress indicator + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptInputProgress: null + }); + } + }); + await waitForAnimationEnd(page); + expect(await page.waitForSelector(`${progressSelector}.no-content`)).toBeDefined(); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/index.ts b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/index.ts new file mode 100644 index 00000000..67c7d387 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/index.ts @@ -0,0 +1,9 @@ +import { renderPromptTopBar } from './render-prompt-top-bar'; +import { promptTopBarTooltip } from './prompt-top-bar-tooltip'; +import { promptTopBarButtonOverlay } from './prompt-top-bar-button-overlay'; + +export { + renderPromptTopBar, + promptTopBarTooltip, + promptTopBarButtonOverlay, +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-button-overlay.ts b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-button-overlay.ts new file mode 100644 index 00000000..8eabe417 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-button-overlay.ts @@ -0,0 +1,125 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const promptTopBarButtonOverlay = async (page: Page): Promise => { + // Set up the prompt top bar with a title and action button + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptTopBarTitle: 'Test Title', + promptTopBarButton: { + id: 'rules-button', + text: 'Rules', + icon: 'check-list' + } + }); + } + }); + + await waitForAnimationEnd(page); + + // Click the top bar button to open the overlay + const topBarButton = page.locator(getSelector(testIds.prompt.topBarButton)); + await topBarButton.click(); + await waitForAnimationEnd(page); + + // Set up the overlay with sample data + // Instead of returning the overlay object with functions, we'll create it in the browser context + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.topBarOverlayController = window.mynahUI.openTopBarButtonOverlay({ + tabId: selectedTabId, + topBarButtonOverlay: { + selectable: 'clickable', + list: [ + { + groupName: 'Test Group', + icon: 'folder', + childrenIndented: true, + actions: [ { id: 'Test Group', icon: 'minus', status: 'clear' } ], + children: [ + { + id: 'test-item-1', + icon: 'check-list', + title: 'Test Item 1', + description: 'Description for test item 1', + actions: [ { id: 'test-item-1', icon: 'ok', status: 'clear' } ], + }, + { + id: 'test-item-2', + icon: 'check-list', + title: 'Test Item 2', + description: 'Description for test item 2', + actions: [ { id: 'test-item-2', status: 'clear' } ], + + } + ] + } + ] + }, + events: { + onGroupClick: () => {}, + onItemClick: () => {}, + onClose: () => {} + } + }); + } + }); + + await waitForAnimationEnd(page); + + // Check if the overlay is visible + const actionOverlay = page.locator(getSelector(testIds.prompt.topBarActionOverlay)); + expect(await actionOverlay.isVisible()).toBeTruthy(); + + // Check if the group title is visible + const groupTitle = page.locator(getSelector(testIds.prompt.quickPicksGroupTitle)); + expect(await groupTitle.isVisible()).toBeTruthy(); + + // Take a screenshot of the overlay with the group + expect(await actionOverlay.screenshot()).toMatchSnapshot(); + + // Test the update function using the stored controller + await page.evaluate(() => { + // Use the stored controller to update the overlay + window.topBarOverlayController.update({ + list: [ + { + groupName: 'Updated Group', + icon: 'folder', + childrenIndented: true, + actions: [ { id: 'Test Group', icon: 'ok', status: 'clear' } ], + children: [ + { + id: 'updated-item-1', + icon: 'check-list', + title: 'Updated Item 1', + description: 'Updated description', + actions: [ { id: 'updated-item-1', icon: 'ok', status: 'clear' } ], + + } + ] + } + ] + }); + }); + + await waitForAnimationEnd(page); + + // Take a screenshot of the updated overlay + expect(await actionOverlay.screenshot()).toMatchSnapshot(); + + // Test the close function using the stored controller + await page.evaluate(() => { + // Use the stored controller to close the overlay + window.topBarOverlayController.close(); + }); + + await waitForAnimationEnd(page); + + // Verify the overlay is closed + expect(await page.locator(getSelector(testIds.prompt.topBarActionOverlay)).count()).toBe(0); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-tooltip.ts b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-tooltip.ts new file mode 100644 index 00000000..74c2a19b --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/prompt-top-bar-tooltip.ts @@ -0,0 +1,50 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const promptTopBarTooltip = async (page: Page): Promise => { + const promptTopBarSelector = getSelector(testIds.prompt.topBar); + + // Set up the prompt top bar with a title and context items + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptTopBarTitle: 'Context Items', + promptTopBarContextItems: [ + { + id: 'context-1', + command: '@workspace', + icon: 'folder', + description: 'Use workspace context for better answers' + } + ] + }); + } + }); + + // Wait for the top bar to be visible + await page.waitForSelector(promptTopBarSelector); + await waitForAnimationEnd(page); + + // Hover over the context pill to show the tooltip + const contextPill = page.locator(getSelector(testIds.prompt.topBarContextPill)).first(); + await contextPill.hover(); + + // Wait for tooltip to be displayed + await justWait(1000); + + // Check if tooltip is visible + const tooltip = page.locator(getSelector(testIds.prompt.topBarContextTooltip)); + expect(await tooltip.isVisible()).toBeTruthy(); + + // Take a screenshot of the tooltip + expect(await tooltip.screenshot()).toMatchSnapshot(); + + // Move mouse away to hide tooltip + await page.mouse.move(0, 0); + await waitForAnimationEnd(page); + + // Verify tooltip is hidden + expect(await page.locator(getSelector(testIds.prompt.topBarContextTooltip)).count()).toBe(0); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/render-prompt-top-bar.ts b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/render-prompt-top-bar.ts new file mode 100644 index 00000000..3bfb9d43 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/prompt-top-bar/render-prompt-top-bar.ts @@ -0,0 +1,172 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const renderPromptTopBar = async (page: Page): Promise => { + const promptTopBarSelector = getSelector(testIds.prompt.topBar); + + // Set up the prompt top bar with a title + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptTopBarTitle: 'Test Title', + }); + } + }); + + // Wait for the top bar to be visible + const promptTopBarWrapper = await page.waitForSelector(promptTopBarSelector); + expect(await promptTopBarWrapper.isVisible()).toBeTruthy(); + await waitForAnimationEnd(page); + + // Take a screenshot of the top bar with just a title + expect(await promptTopBarWrapper.screenshot()).toMatchSnapshot(); + + // Add context items to the top bar + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptTopBarContextItems: [ + { + id: 'context-1', + command: '@context1', + icon: 'file', + description: 'First context item' + }, + { + id: 'context-2', + command: '@context2', + icon: 'folder', + description: 'Second context item' + } + ] + }); + } + }); + + await waitForAnimationEnd(page); + + // Take a screenshot of the top bar with title and context items + expect(await promptTopBarWrapper.screenshot()).toMatchSnapshot(); + + // Add a top bar button + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptTopBarButton: { + id: 'top-bar-button', + text: 'Actions', + icon: 'menu' + } + }); + } + }); + + await waitForAnimationEnd(page); + + // Take a screenshot of the complete top bar + expect(await promptTopBarWrapper.screenshot()).toMatchSnapshot(); + + // Test removing a context item by clicking on it + const firstContextPill = page.locator(getSelector(testIds.prompt.topBarContextPill)).first(); + await firstContextPill.hover(); + await waitForAnimationEnd(page); + + // Take a screenshot of the hover state + expect(await firstContextPill.screenshot()).toMatchSnapshot(); + + // Click to remove the context item + await firstContextPill.click(); + await waitForAnimationEnd(page); + + // Verify only one context item remains + expect(await page.locator(getSelector(testIds.prompt.topBarContextPill)).count()).toBe(1); + + // Test overflow behavior by adding more context items + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptTopBarContextItems: [ + { + id: 'context-2', + command: 'context2', + icon: 'magic', + description: 'Second context item' + }, + { + id: 'context-3', + command: 'context3', + icon: 'file', + description: 'Third context item' + }, + { + id: 'context-4', + command: 'context4', + icon: 'folder', + description: 'Fourth context item' + }, + { + id: 'context-5', + command: 'context5', + icon: 'file', + description: 'Fifth context item' + }, + { + id: 'context-6', + command: 'context6', + icon: 'file', + description: 'Sixth context item' + } + ] + }); + } + }); + + await waitForAnimationEnd(page); + + // Check if overflow button appears + const overflowButton = page.locator(getSelector(testIds.prompt.topBarOverflowPill)); + expect(await overflowButton.isVisible()).toBeTruthy(); + + // Take a screenshot with overflow button + expect(await promptTopBarWrapper.screenshot()).toMatchSnapshot(); + + // Click the overflow button to show the overlay + await overflowButton.click(); + await waitForAnimationEnd(page); + + // Wait for overlay to appear + await justWait(1000); + + // Take a screenshot of the overflow overlay + const overflowOverlay = page.locator(getSelector(testIds.prompt.topBarOverflowOverlay)); + expect(await overflowOverlay.screenshot()).toMatchSnapshot(); + + // Click outside to close the overlay + await page.mouse.click(10, 10); + await waitForAnimationEnd(page); + + // Test the top bar button click + const topBarButton = page.locator(getSelector(testIds.prompt.topBarButton)); + await topBarButton.click(); + await waitForAnimationEnd(page); + + // Test hiding the top bar by clearing the title + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + promptTopBarTitle: '', + }); + } + }); + + await waitForAnimationEnd(page); + + // Verify the top bar is hidden + expect(await page.locator(getSelector(testIds.prompt.topBar) + '.hidden').count()).toBe(1); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/quick-action-commands-header.ts b/vendor/mynah-ui/ui-tests/__test__/flows/quick-action-commands-header.ts new file mode 100644 index 00000000..c90e28b6 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/quick-action-commands-header.ts @@ -0,0 +1,159 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const renderQuickActionCommandsHeader = async (page: Page, skipScreenshots?: boolean): Promise => { + await waitForAnimationEnd(page); + + // Clear the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await waitForAnimationEnd(page); + + // Press '/' to trigger quick action commands + await input.press('/'); + await waitForAnimationEnd(page); + + // Check that the quick picks wrapper is visible + const quickPicksWrapper = page.locator(getSelector(testIds.prompt.quickPicksWrapper)).nth(-1); + expect(quickPicksWrapper).toBeDefined(); + expect(await quickPicksWrapper.isVisible()).toBeTruthy(); + + // Check that the quick action commands header is present (it's rendered in an overlay) + const headerElement = page.locator('.mynah-chat-prompt-quick-picks-header').first(); + expect(headerElement).toBeDefined(); + expect(await headerElement.isVisible()).toBeTruthy(); + + // Verify header contains expected elements + const headerIcon = headerElement.locator('.mynah-ui-icon').first(); + const headerTitle = headerElement.locator('[data-testid$="-title"]').first(); + const headerDescription = headerElement.locator('[data-testid$="-description"]').first(); + + expect(await headerIcon.isVisible()).toBeTruthy(); + expect(await headerTitle.isVisible()).toBeTruthy(); + expect(await headerDescription.isVisible()).toBeTruthy(); + + // Verify header content + const titleText = await headerTitle.textContent(); + const descriptionText = await headerDescription.textContent(); + + expect(titleText).toContain('Q Developer agentic capabilities'); + expect(descriptionText).toContain('You can now ask Q directly in the chat'); + + // Check for status-specific styling (warning status in the example) + const hasWarningStatus = await headerElement.evaluate((el) => + el.classList.contains('status-warning') + ); + expect(hasWarningStatus).toBeTruthy(); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot('quick-action-commands-header.png'); + } + + // Clean up - press Backspace to close the quick picks + await input.press('Backspace'); + await waitForAnimationEnd(page); +}; + +export const verifyQuickActionCommandsHeaderInteraction = async (page: Page, skipScreenshots?: boolean): Promise => { + await waitForAnimationEnd(page); + + // Clear the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await waitForAnimationEnd(page); + + // Press '/' to trigger quick action commands + await input.press('/'); + await waitForAnimationEnd(page); + + const headerElement = page.locator('.mynah-chat-prompt-quick-picks-header').first(); + + // Hover over the header to check for any hover effects + await headerElement.hover(); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot('quick-action-commands-header-hover.png'); + } + + // Verify the header remains visible during interaction + expect(await headerElement.isVisible()).toBeTruthy(); + + // Clean up + await input.press('Backspace'); + await waitForAnimationEnd(page); +}; + +export const verifyQuickActionCommandsHeaderWithoutData = async (page: Page, skipScreenshots?: boolean): Promise => { + // This test would verify behavior when quickActionCommandsHeader is null or undefined + // For now, we'll simulate a scenario where the header should not be displayed + + await waitForAnimationEnd(page); + + // Clear the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await waitForAnimationEnd(page); + + // Press '@' to trigger context commands (which typically don't have the header) + await input.press('@'); + await waitForAnimationEnd(page); + + const quickPicksWrapper = page.locator(getSelector(testIds.prompt.quickPicksWrapper)).nth(-1); + expect(await quickPicksWrapper.isVisible()).toBeTruthy(); + + // Verify that the quick action commands header is NOT present for context commands + const headerElements = page.locator('.mynah-chat-prompt-quick-picks-header'); + const headerCount = await headerElements.count(); + expect(headerCount).toBe(0); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot('quick-action-commands-header-not-present.png'); + } + + // Clean up + await input.press('Backspace'); + await waitForAnimationEnd(page); +}; + +export const verifyQuickActionCommandsHeaderStatusVariations = async (page: Page, skipScreenshots?: boolean): Promise => { + // This test would verify different status variations (warning, error, success, etc.) + // Since we can't easily change the status in the test, we'll verify the current status styling + + await waitForAnimationEnd(page); + + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await waitForAnimationEnd(page); + + await input.press('/'); + await waitForAnimationEnd(page); + + const headerElement = page.locator('.mynah-chat-prompt-quick-picks-header').first(); + + // Check for various possible status classes + const statusClasses = [ 'status-warning', 'status-error', 'status-success', 'status-info', 'status-default' ]; + let foundStatusClass = false; + + for (const statusClass of statusClasses) { + const hasStatus = await headerElement.evaluate((el, className) => + el.classList.contains(className), statusClass + ); + if (hasStatus) { + foundStatusClass = true; + console.log(`Found status class: ${statusClass}`); + break; + } + } + + expect(foundStatusClass).toBeTruthy(); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot('quick-action-commands-header-status.png'); + } + + // Clean up + await input.press('Backspace'); + await waitForAnimationEnd(page); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/close-quick-picks.ts b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/close-quick-picks.ts new file mode 100644 index 00000000..8a79e0de --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/close-quick-picks.ts @@ -0,0 +1,44 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const closeQuickPicks = async (page: Page, method: 'blur' | 'escape' | 'space', mode: 'command' | 'context', skipScreenshots?: boolean): Promise => { + // Press '/' in the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.focus(); + await waitForAnimationEnd(page); + await input.press(mode === 'context' ? '@' : '/'); + await waitForAnimationEnd(page); + + // Find the command selector + const commandSelector = page.locator(getSelector(testIds.prompt.quickPicksWrapper)).nth(-1); + expect(commandSelector).toBeDefined(); + expect(await commandSelector.isVisible()).toBeTruthy(); + + // Either click outside to blur, or press escape + if (method === 'blur') { + await page.mouse.click(100, 400); + } else if (method === 'escape') { + await input.press('Escape'); + } else if (method === 'space') { + await input.press(' '); + } + await waitForAnimationEnd(page); + + // Now the command selector should be closed, but the input should still remain intact + expect(await commandSelector.isVisible()).toBeFalsy(); + if (mode === 'context') { + expect(await input.innerText()).toBe(method === 'space' ? '@ ' : '@'); + } else if (mode === 'command') { + expect(await input.innerText()).toBe(method === 'blur' ? '/' : method === 'space' ? '/ ' : ''); + } + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + if (mode === 'context') { + await input.clear(); + await input.press('Backspace'); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/filter-quick-picks.ts b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/filter-quick-picks.ts new file mode 100644 index 00000000..47efaa7b --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/filter-quick-picks.ts @@ -0,0 +1,35 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const filterQuickPicks = async (page: Page, mode?: 'command' | 'context', skipScreenshots?: boolean): Promise => { + // Clear the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await input.press('Backspace'); + await waitForAnimationEnd(page); + + // Type a '/' character, followed by a first character + await input.press(mode === 'context' ? '@' : '/'); + await waitForAnimationEnd(page); + await input.press(mode === 'context' ? 'w' : 'h'); + await waitForAnimationEnd(page); + + // Check that the command selector is opened, and visible + const commandSelector = page.locator(getSelector(testIds.prompt.quickPicksWrapper)).nth(-1); + expect(commandSelector).toBeDefined(); + expect(await commandSelector.isVisible()).toBeTruthy(); + + // Check that there is only one suggestion + const quickPickItem = page.locator(getSelector(testIds.prompt.quickPickItem)); + expect(await quickPickItem.count()).toBe(1); + + // Check that the suggestions are what we expect from the first character + const innerTexts = (await quickPickItem.allInnerTexts()); + expect(innerTexts[0]).toContain(mode === 'context' ? '@workspace' : '/help'); + expect(innerTexts[0]).not.toContain(mode === 'context' ? '@file' : '/clear'); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/render-quick-picks-header.ts b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/render-quick-picks-header.ts new file mode 100644 index 00000000..691db37e --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/render-quick-picks-header.ts @@ -0,0 +1,75 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const renderQuickPicksHeader = async (page: Page, skipScreenshots?: boolean): Promise => { + await waitForAnimationEnd(page); + + // Update store to add quickActionCommandsHeader + await page.evaluate(() => { + (window as any).mynahUI.updateStore('tab-1', { + quickActionCommands: [ + { + groupName: 'Code Generation', + commands: [ + { + command: '/dev', + description: 'Generate code for your task', + placeholder: 'Describe what you want to build' + } + ] + } + ], + quickActionCommandsHeader: { + status: 'warning', + icon: 'info', + title: 'New agentic capabilities', + description: 'You can now ask Q directly in the chat. You don\'t need to explicitly use /dev, /test, or /doc commands anymore.' + } + }); + }); + + await waitForAnimationEnd(page); + + // Clear the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await waitForAnimationEnd(page); + + // Press '/' in the input to trigger quick picks + await input.press('/'); + await waitForAnimationEnd(page); + + // Check that the command selector is opened and visible + const commandSelector = page.locator(getSelector(testIds.prompt.quickPicksWrapper)).nth(-1); + expect(commandSelector).toBeDefined(); + expect(await commandSelector.isVisible()).toBeTruthy(); + + // Check that the header is present and contains expected content + const headerElement = commandSelector.locator('.mynah-chat-prompt-quick-picks-header').first(); + expect(await headerElement.isVisible()).toBeTruthy(); + + // Verify header content + const headerText = await headerElement.textContent(); + expect(headerText).toContain('New agentic capabilities'); + expect(headerText).toContain('You can now ask Q directly in the chat'); + + // Verify header status styling (warning status should add appropriate class) + const headerClasses = await headerElement.getAttribute('class'); + expect(headerClasses).toContain('status-warning'); + + // Verify icon is present + const headerIcon = headerElement.locator('.mynah-ui-title-description-icon-icon').first(); + expect(await headerIcon.isVisible()).toBeTruthy(); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + // Clean up - close the quick picks by pressing Backspace + await input.press('Backspace'); + await waitForAnimationEnd(page); + + // Verify quick picks are closed + expect(await commandSelector.isVisible()).toBeFalsy(); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/render-quick-picks.ts b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/render-quick-picks.ts new file mode 100644 index 00000000..ad509468 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/render-quick-picks.ts @@ -0,0 +1,28 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const renderQuickPicks = async (page: Page, mode?: 'command' | 'context', skipScreenshots?: boolean): Promise => { + await waitForAnimationEnd(page); + + // Clear the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.clear(); + await waitForAnimationEnd(page); + + // Press '/' in the input + await input.press(mode === 'context' ? '@' : '/'); + await waitForAnimationEnd(page); + + // Check that the command selector is opened, and visible + const commandSelector = page.locator(getSelector(testIds.prompt.quickPicksWrapper)).nth(-1); + expect(commandSelector).toBeDefined(); + expect(await commandSelector.isVisible()).toBeTruthy(); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + // Clean up + await input.press('Backspace'); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/select-quick-picks.ts b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/select-quick-picks.ts new file mode 100644 index 00000000..a28f801d --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/quick-picks/select-quick-picks.ts @@ -0,0 +1,31 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../../helpers'; +import testIds from '../../../../src/helper/test-ids'; + +export const selectQuickPicks = async (page: Page, method: 'click' | 'Tab' | 'Enter' | 'Space', mode?: 'command' | 'context', skipScreenshots?: boolean): Promise => { + // Clear the input + const input = page.locator(getSelector(testIds.prompt.input)); + await input.press('Backspace'); + await input.press('Backspace'); + await waitForAnimationEnd(page); + + // Type a '/' character, followed by a first character + await input.press(mode === 'context' ? '@' : '/'); + await waitForAnimationEnd(page); + await input.press(mode === 'context' ? 'w' : 'h'); + await waitForAnimationEnd(page); + if (method !== 'click') { + await input.press(method); + } else { + await page.locator(getSelector(testIds.prompt.quickPickItem)).click(); + } + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + // Remove the selection again + await input.press('Backspace'); + await waitForAnimationEnd(page); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/render-buttons.ts b/vendor/mynah-ui/ui-tests/__test__/flows/render-buttons.ts new file mode 100644 index 00000000..12ee09ee --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/render-buttons.ts @@ -0,0 +1,93 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const renderButtons = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + messageId: new Date().getTime().toString(), + body: 'This is a card with actions inside!', + buttons: [ + { + text: 'Action 1', + id: 'action-1', + status: 'info', + icon: 'chat' as any + }, + { + text: 'Action 2', + description: 'This action will not remove the card!', + id: 'action-2', + keepCardAfterClick: false, + }, + { + text: 'Action 3', + description: 'This is disabled for some reason!', + id: 'action-3', + disabled: true, + }, + { + text: 'Primary', + description: 'This is colored!', + id: 'action-3', + status: 'primary', + }, + { + text: 'Main', + description: 'This is more colored!', + id: 'action-3', + status: 'main', + }, + { + text: 'Clear', + description: 'This is clear!', + id: 'action-3', + status: 'clear', + }, + ], + }); + + window.mynahUI.addChatItem(selectedTabId, { + type: 'answer' as any, + messageId: new Date().getTime().toString(), + body: 'Do you wish to continue?', + buttons: [ + { + id: 'confirmation-buttons-cancel', + text: 'Cancel', + status: 'error', + icon: 'cancel-circle' as any, + position: 'outside' + }, + { + id: 'confirmation-buttons-confirm', + text: 'Confirm', + status: 'success', + icon: 'ok-circled' as any, + position: 'outside' + }, + ] + }); + } + }); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const locator1 = page.locator(answerCardSelector).nth(0); + await locator1.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + expect(await locator1.screenshot()).toMatchSnapshot(); + } + + const locator2 = page.locator(answerCardSelector).nth(1); + await locator2.scrollIntoViewIfNeeded(); + if (skipScreenshots !== true) { + await expect(await locator2.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/render-character-count.ts b/vendor/mynah-ui/ui-tests/__test__/flows/render-character-count.ts new file mode 100644 index 00000000..64cb6831 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/render-character-count.ts @@ -0,0 +1,27 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, isVisible, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const renderCharacterCount = async (page: Page, skipScreenshots?: boolean): Promise => { + const characterCounter = page.locator(getSelector(testIds.prompt.remainingCharsIndicator)); + expect(characterCounter).toBeDefined(); + + // Check if the element is not visible initially + expect(await characterCounter.evaluate(isVisible)).toBe(false); + + // Fill the input with 3500 characters + await page.locator(getSelector(testIds.prompt.input)).fill('z'.repeat(3500)); + expect(characterCounter).toBeDefined(); + + // Check if the element is visible after filling the input + expect(await characterCounter.evaluate(isVisible)).toBe(true); + + // Check that the value is set to 3500/4000 + expect(await characterCounter.innerText()).toBe('3500/4000'); + + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/render-information-card.ts b/vendor/mynah-ui/ui-tests/__test__/flows/render-information-card.ts new file mode 100644 index 00000000..0cfab238 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/render-information-card.ts @@ -0,0 +1,41 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const renderInformationCard = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + messageId: new Date().getTime().toString(), + type: 'answer' as any, + informationCard: { + title: 'Information card', + description: 'With a description below the title and success status.', + icon: 'bug' as any, + content: { + body: 'This is a list below with some code and bolds inside\n- **Bold** Text with some `inline code`.\n- Also with some code blocks ```const a = 5;```\n\nEnd of the list.\nList with numbers.\n1. **Item1** This is the first list item.\n2. **Item2** This is the second list item.\n3. **Item3** This is the third list item.\n4. **Item4** This is the fourth list item. And it also has a [LINK](#) inside.', + }, + status: { + status: 'success', + icon: 'thumbs-up' as any, + body: 'Successfully completed this task!' + } + }, + }); + } + }); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const answerCard = await page.waitForSelector(answerCardSelector); + await answerCard.scrollIntoViewIfNeeded(); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/render-tabbed-card.ts b/vendor/mynah-ui/ui-tests/__test__/flows/render-tabbed-card.ts new file mode 100644 index 00000000..82407d3b --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/render-tabbed-card.ts @@ -0,0 +1,66 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const renderTabbedCard = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.evaluate(() => { + const selectedTabId = window.mynahUI.getSelectedTabId(); + if (selectedTabId != null) { + window.mynahUI.updateStore(selectedTabId, { + chatItems: [], + }); + + window.mynahUI.addChatItem(selectedTabId, { + messageId: new Date().getTime().toString(), + type: 'answer' as any, + body: '### Feature Development\nGenerate code across files with a task description.', + buttons: [ + { + id: 'quick-start', + text: 'Quick start with \'**/dev**\'', + icon: 'right-open' as any + } + ], + tabbedContent: [ + { + value: 'overview', + label: 'Overview', + icon: 'comment' as any, + selected: true, + content: { + body: 'Overview content' + } + }, + { + value: 'examples', + label: 'Examples', + icon: 'play' as any, + selected: false, + content: { + body: 'Examples content' + } + } + ] + } + ); + } + }); + await waitForAnimationEnd(page); + + const answerCardSelector = getSelector(testIds.chatItem.type.answer); + const answerCard = await page.waitForSelector(answerCardSelector); + await answerCard.scrollIntoViewIfNeeded(); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } + + // Change selected item, check for content change + const locator = page.locator(`${getSelector(`${testIds.chatItem.tabbedCard.tabs}-option-wrapper`)}`).last(); + await locator.click(); + await waitForAnimationEnd(page); + + if (skipScreenshots !== true) { + expect(await page.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/render-user-prompt.ts b/vendor/mynah-ui/ui-tests/__test__/flows/render-user-prompt.ts new file mode 100644 index 00000000..48e94f7b --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/render-user-prompt.ts @@ -0,0 +1,23 @@ +import { expect, Page } from 'playwright/test'; +import { getSelector, justWait, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; + +export const renderUserPrompt = async (page: Page, skipScreenshots?: boolean): Promise => { + await page.locator(getSelector(testIds.prompt.input)).fill('This is a user Prompt'); + await page.locator(getSelector(testIds.prompt.send)).nth(1).click(); + const promptInput = await page.waitForSelector(getSelector(testIds.prompt.input)); + expect(await promptInput.getAttribute('disabled')).toEqual('disabled'); + + const userCardSelector = getSelector(testIds.chatItem.type.prompt); + const userCard = await page.waitForSelector(userCardSelector); + expect(userCard).toBeDefined(); + await waitForAnimationEnd(page); + await userCard.scrollIntoViewIfNeeded(); + expect(await promptInput.getAttribute('disabled')).toEqual(null); + await waitForAnimationEnd(page); + await justWait(50); + + if (skipScreenshots !== true) { + expect(await userCard.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/welcome-mode.ts b/vendor/mynah-ui/ui-tests/__test__/flows/welcome-mode.ts new file mode 100644 index 00000000..f2af102e --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/welcome-mode.ts @@ -0,0 +1,68 @@ +import { Page } from 'playwright'; +import { getSelector, waitForAnimationEnd } from '../helpers'; +import testIds from '../../../src/helper/test-ids'; +import { closeTab } from './close-tab'; +import { expect } from 'playwright/test'; + +export const welcomeMode = async (page: Page, skipScreenshots?: boolean): Promise => { + await closeTab(page, false, true); + const welcomeCardId = 'welcome-screen-card'; + + const newTabId = await page.evaluate((cardId) => { + return window.mynahUI.updateStore('', { + tabTitle: 'Welcome', + promptInputPlaceholder: 'Placeholder', + promptInputInfo: 'Footer', + chatItems: [ + { + type: 'answer' as any, + body: 'Welcome card', + messageId: cardId, + icon: 'asterisk' as any, + } + ] + }) as string; + }, welcomeCardId); + + await page.mouse.move(0, 0); + const chatWrapperSelector = `${getSelector(testIds.chat.wrapper)}[mynah-tab-id="${String(newTabId)}"]`; + const chatWrapper = await page.waitForSelector(chatWrapperSelector); + expect(chatWrapper).toBeDefined(); + + const welcomeCardSelector = `${getSelector(testIds.chatItem.type.answer)}[messageid="${welcomeCardId}"]`; + const welcomeCard = await page.waitForSelector(welcomeCardSelector); + await waitForAnimationEnd(page); + expect(welcomeCard).toBeDefined(); + + if (skipScreenshots !== true) { + // snap + expect(await chatWrapper.screenshot()).toMatchSnapshot(); + } + + await page.evaluate((tabId) => { + window.mynahUI.updateStore(tabId, { + tabBackground: true, + compactMode: true, + tabHeaderDetails: { + icon: 'q' as any, + title: 'Amazon Q Developer', + description: 'Welcome to Amazon Q Developer' + }, + promptInputLabel: 'Ask your question' + }); + }, newTabId); + await waitForAnimationEnd(page); + const chatWrapperClasses = await (await page.waitForSelector(chatWrapperSelector)).evaluate(el => Array.from(el.classList)); + expect(chatWrapperClasses).toContain('with-background'); + + expect(await page.waitForSelector(getSelector(testIds.prompt.label))).toBeDefined(); + expect(await page.waitForSelector(getSelector(testIds.chat.header))).toBeDefined(); + expect(await page.waitForSelector(getSelector(`${testIds.chat.header}-icon`))).toBeDefined(); + expect(await page.waitForSelector(getSelector(`${testIds.chat.header}-title`))).toBeDefined(); + expect(await page.waitForSelector(getSelector(`${testIds.chat.header}-description`))).toBeDefined(); + + if (skipScreenshots !== true) { + // snap + expect(await chatWrapper.screenshot()).toMatchSnapshot(); + } +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/flows/window-boundaries.ts b/vendor/mynah-ui/ui-tests/__test__/flows/window-boundaries.ts new file mode 100644 index 00000000..6d6233df --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/flows/window-boundaries.ts @@ -0,0 +1,69 @@ +import { expect, Page } from 'playwright/test'; +import testIds from '../../../src/helper/test-ids'; +import { DEFAULT_VIEWPORT, getOffsetHeight, getSelector, justWait } from '../helpers'; +import { clickToFollowup } from './click-followup'; + +export const checkContentInsideWindowBoundaries = async (page: Page): Promise => { + await page.mouse.move(0, 0); + const chatItemsContainer = await page.waitForSelector(getSelector(testIds.chat.chatItemsContainer)); + const footerPanel = await page.waitForSelector(getSelector(testIds.prompt.footerInfo)); + expect(footerPanel).toBeDefined(); + expect(getOffsetHeight(await footerPanel.boundingBox())).toBeLessThanOrEqual(page.viewportSize()?.height ?? 0); + + // Add content to create a scroll area + await clickToFollowup(page, true); + + await justWait(500); + chatItemsContainer.evaluate(elm => { + elm.scrollTop = 1000000; + }); + + // Check if the footer element exceeds from bottom + expect(getOffsetHeight(await footerPanel.boundingBox())).toBeLessThanOrEqual(page.viewportSize()?.height ?? 0); + + // Snap + expect(await page.screenshot()).toMatchSnapshot(); + + // Scroll to top to the init message + await (await page.waitForSelector(`${getSelector(testIds.chatItem.type.answer)}[messageid="welcome-message"]`)).scrollIntoViewIfNeeded(); + + // Check if the footer element exceeds from bottom + expect(getOffsetHeight(await footerPanel.boundingBox())).toBeLessThanOrEqual(page.viewportSize()?.height ?? 0); + + // Update viewport size + await page.setViewportSize({ + width: 350, + height: 500 + }); + + await justWait(100); + + // Check if the footer element exceeds from bottom + expect(getOffsetHeight(await footerPanel.boundingBox())).toBeLessThanOrEqual(page.viewportSize()?.height ?? 0); + + // Snap + expect(await page.screenshot()).toMatchSnapshot(); + + // Set viewport size to + await page.setViewportSize({ + width: 1, + height: 1 + }); + // We don't need to wait here, we're just checking if the viewport width is changed or not + expect(page.viewportSize()?.width).toBeLessThanOrEqual(1); + + // Revert viewport size + await page.setViewportSize(DEFAULT_VIEWPORT); + + await justWait(100); + + // Check if the footer element exceeds from bottom + expect(getOffsetHeight(await footerPanel.boundingBox())).toBeLessThanOrEqual(page.viewportSize()?.height ?? 0); + + await justWait(500); + await chatItemsContainer.evaluate(node => { node.scrollTop = 0; }); + + await justWait(100); + // Snap + expect(await page.screenshot()).toMatchSnapshot(); +}; diff --git a/vendor/mynah-ui/ui-tests/__test__/helpers.ts b/vendor/mynah-ui/ui-tests/__test__/helpers.ts new file mode 100644 index 00000000..c01a067a --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/helpers.ts @@ -0,0 +1,55 @@ +import { Page } from 'playwright/test'; +import testIds from '../../src/helper/test-ids'; +export const DEFAULT_VIEWPORT = { + width: 500, + height: 950 +}; + +export const getOffsetHeight = (boxRect: { + width?: number; + height?: number; + x?: number; + y?: number; +} | null): number => { + return boxRect != null ? (boxRect?.y ?? 0) + (boxRect?.height ?? 0) : 0; +}; + +export const normalizeText = (text: string): string => text.replace(/\r\n/g, '\n').trim(); + +export const waitForAnimationEnd = async (page: Page): Promise => { + return await Promise.race([ + new Promise((resolve) => setTimeout(resolve, 10000)), + page.evaluate(async () => { + return await new Promise((resolve) => { + const startTime = new Date().getTime(); + const animationStateCheckInterval: ReturnType = setInterval(() => { + const allAnims = document.getAnimations(); + if (allAnims.find((anim) => anim.playState !== 'finished') == null || new Date().getTime() - startTime > 6000) { + clearInterval(animationStateCheckInterval); + // Give a delay to make the render complete + setTimeout(() => { + resolve(); + }, 550); + } + }, 550); + }); + }), + ]); +}; + +export async function justWait (duration: number): Promise { + return await new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, duration); + }); +} + +export function getSelector (selector: string): string { + return `css=[${testIds.selector}="${selector}"]`; +} + +export function isVisible (el: SVGElement | HTMLElement): boolean { + const style = window.getComputedStyle(el); + return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'; +} diff --git a/vendor/mynah-ui/ui-tests/__test__/main.spec.ts b/vendor/mynah-ui/ui-tests/__test__/main.spec.ts new file mode 100644 index 00000000..91708c71 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/__test__/main.spec.ts @@ -0,0 +1,344 @@ +import { test } from '@playwright/test'; +import { initRender } from './flows/init-render'; +import { renderUserPrompt } from './flows/render-user-prompt'; +import { clickToFollowup } from './flows/click-followup'; +import { closeTab } from './flows/close-tab'; +import { openNewTab } from './flows/open-new-tab'; +import { renderQuickPicks } from './flows/quick-picks/render-quick-picks'; +import { closeQuickPicks } from './flows/quick-picks/close-quick-picks'; +import { filterQuickPicks } from './flows/quick-picks/filter-quick-picks'; +import { selectQuickPicks } from './flows/quick-picks/select-quick-picks'; +import { renderCharacterCount } from './flows/render-character-count'; +import { + renderQuickActionCommandsHeader, + verifyQuickActionCommandsHeaderInteraction, + verifyQuickActionCommandsHeaderWithoutData, + verifyQuickActionCommandsHeaderStatusVariations +} from './flows/quick-action-commands-header'; +import { progressIndicator } from './flows/prompt-progress-indicator'; +import { parseMarkdown } from './flows/markdown-parser/markdown-parser'; +import { renderInformationCard } from './flows/render-information-card'; +import { renderTabbedCard } from './flows/render-tabbed-card'; +import { welcomeMode } from './flows/welcome-mode'; +import { renderButtons } from './flows/render-buttons'; +import { hoverOverLink } from './flows/link-hover-preview'; +import { showFileTree } from './flows/file-tree/show-file-tree'; +import { collapseExpandFileTree } from './flows/file-tree/collapse-file-tree'; +import { showFileTooltip } from './flows/file-tree/show-file-tooltip'; +import { triggerFileActions } from './flows/file-tree/trigger-file-action'; +import { renderFileDetails } from './flows/file-tree/render-file-details'; +import { showFilePills, showFilePillsWithDeletedFiles } from './flows/file-pills/file-pills'; +import { renderFormElements } from './flows/form/render-form-elements'; +import { disableForm } from './flows/form/disable-form'; +import { removeForm } from './flows/form/remove-form'; +import { renderVoteButtons } from './flows/feedback-form/render-vote-buttons'; +import { renderUpvoteResult } from './flows/feedback-form/render-upvote-result'; +import { renderDownvoteResult } from './flows/feedback-form/render-downvote-result'; +import { renderFeedbackForm } from './flows/feedback-form/render-feedback-form'; +import { cancelFeedbackForm } from './flows/feedback-form/cancel-feedback-form'; +import { submitFeedbackForm } from './flows/feedback-form/submit-feedback-form'; +import { stayOnCurrentPrompt } from './flows/navigate-prompts/stay-on-current-prompt'; +import { navigatePromptsDown } from './flows/navigate-prompts/navigate-prompts-down'; +import { navigatePromptsUp } from './flows/navigate-prompts/navigate-prompts-up'; +import { navigatePromptsToEmpty } from './flows/navigate-prompts/navigate-prompts-to-empty'; +import { navigateBackToCurrentPrompt } from './flows/navigate-prompts/navigate-back-to-current-prompt'; +import { navigateBackToCurrentPromptWithCodeAttachment } from './flows/navigate-prompts/navigate-back-to-current-prompt-with-code-attachment'; +import { promptOptions } from './flows/prompt-options'; +import { renderIcons } from './flows/icons'; +import { renderMutedCards } from './flows/muted-cards'; +import { checkContentInsideWindowBoundaries } from './flows/window-boundaries'; +import { navigatePromptsFirstLastLineCheck } from './flows/navigate-prompts/navigate-prompts-first-last-line-check'; +import { renderHeaders } from './flows/headers'; +import { renderAndDismissCard } from './flows/dismissible-cards'; +import { openCloseDropdown } from './flows/dropdown-list/open-close-dropdown'; +import { selectDropdownOption } from './flows/dropdown-list/select-dropdown-option'; +import { renderDisabledText } from './flows/disabled-text'; +import { DEFAULT_VIEWPORT } from './helpers'; +import path from 'path'; +import { + renderPromptTopBar, + promptTopBarTooltip, + promptTopBarButtonOverlay, +} from './flows/prompt-top-bar'; + +test.describe('Open MynahUI', () => { + test.beforeEach(async ({ page }) => { + const htmlFilePath: string = path.join(__dirname, '../dist/index.html'); + const fileUrl = `file://${htmlFilePath}`; + await page.setViewportSize(DEFAULT_VIEWPORT); + await page.goto(fileUrl, { waitUntil: 'domcontentloaded' }); + }); + + test('should render initial data', async ({ page }) => { + await initRender(page); + }); + + test('should render welcome structure', async ({ page }) => { + await welcomeMode(page); + }); + + test('should show prompt options', async ({ page }) => { + await promptOptions(page); + }); + + test('should show progress indicator', async ({ page }) => { + await progressIndicator(page); + }); + + test('should render user prompt', async ({ page }) => { + await renderUserPrompt(page); + }); + + test('should render new card when followup click', async ({ page }) => { + await clickToFollowup(page); + }); + + test.describe('Tabs', () => { + test('should close the tab', async ({ page }) => { + await closeTab(page); + }); + + test('should open a new tab', async ({ page }) => { + await openNewTab(page); + }); + + test('should close the tab with middle click', async ({ page }) => { + await closeTab(page, true, true); + }); + + test('should open a new tab with double click', async ({ page }) => { + await openNewTab(page, true, true); + }); + }); + + test('should render character limit counter', async ({ page }) => { + await renderCharacterCount(page); + }); + + test('should render information cards correctly', async ({ page }) => { + await renderInformationCard(page); + }); + + test('should render tabbed cards correctly', async ({ page }) => { + await renderTabbedCard(page); + }); + + test.describe('Quick command selector', () => { + test('should render the quick command selector', async ({ page }) => { + await renderQuickPicks(page); + }); + test('should close the quick command selector by clicking outside', async ({ page }) => { + await closeQuickPicks(page, 'blur', 'command'); + }); + test('should close the quick command selector by pressing escape', async ({ page }) => { + await closeQuickPicks(page, 'escape', 'command'); + }); + test('should close the quick command selector by pressing space', async ({ page }) => { + await closeQuickPicks(page, 'space', 'command'); + }); + test('should filter quick command selector list', async ({ page }) => { + await filterQuickPicks(page); + }); + test('should select quick command selector item by clicking', async ({ page }) => { + await selectQuickPicks(page, 'click'); + }); + test('should select quick command selector item with tab', async ({ page }) => { + await selectQuickPicks(page, 'Tab'); + }); + test('should select quick command selector item with enter', async ({ page }) => { + await selectQuickPicks(page, 'Enter'); + }); + }); + + test.describe('Quick Action Commands Header', () => { + test('should render the quick action commands header', async ({ page }) => { + await renderQuickActionCommandsHeader(page); + }); + test('should handle quick action commands header interaction', async ({ page }) => { + await verifyQuickActionCommandsHeaderInteraction(page); + }); + test('should not render header when not applicable', async ({ page }) => { + await verifyQuickActionCommandsHeaderWithoutData(page); + }); + test('should render header with correct status styling', async ({ page }) => { + await verifyQuickActionCommandsHeaderStatusVariations(page); + }); + }); + + test.describe('Context selector', () => { + test('should render the context selector', async ({ page }) => { + await renderQuickPicks(page, 'context'); + }); + test('should close the context selector by clicking outside', async ({ page }) => { + await closeQuickPicks(page, 'blur', 'context'); + }); + test('should close the context selector by pressing escape', async ({ page }) => { + await closeQuickPicks(page, 'escape', 'context'); + }); + test('should close the context selector by pressing space', async ({ page }) => { + await closeQuickPicks(page, 'space', 'context'); + }); + test('should filter context selector list', async ({ page }) => { + await filterQuickPicks(page, 'context'); + }); + test('should select context selector item by clicking', async ({ page }) => { + await selectQuickPicks(page, 'click', 'context'); + }); + test('should select context selector item with tab', async ({ page }) => { + await selectQuickPicks(page, 'Tab', 'context'); + }); + test('should select context selector item with enter', async ({ page }) => { + await selectQuickPicks(page, 'Enter', 'context'); + }); + }); + + test.describe('Prompt Top Bar', () => { + test('should render prompt top bar with title, context items, and button', async ({ page }) => { + await renderPromptTopBar(page); + }); + + test('should show tooltip when hovering over pinned context items', async ({ page }) => { + await promptTopBarTooltip(page); + }); + + test('should show overlay when clicking top bar button', async ({ page }) => { + await promptTopBarButtonOverlay(page); + }); + }); + + test.describe('File tree', () => { + test('should show file tree', async ({ page }) => { + await showFileTree(page); + }); + + test('should collapse and expand file in folders', async ({ page }) => { + await collapseExpandFileTree(page); + }); + + test('should show tooltip with file description on hover', async ({ page }) => { + await showFileTooltip(page); + }); + + test('should trigger default or sub action on click', async ({ page }) => { + await triggerFileActions(page); + }); + + test('should render file appearance based on its details', async ({ page }) => { + await renderFileDetails(page); + }); + }); + + test.describe('File pills', () => { + test('should render file pills in header', async ({ page }) => { + await showFilePills(page); + }); + + test('should render deleted files with special styling', async ({ page }) => { + await showFilePillsWithDeletedFiles(page); + }); + }); + + test('should show link preview in tooltip on link hover', async ({ page }) => { + await hoverOverLink(page); + }); + + test('should render buttons on cards correctly', async ({ page }) => { + await renderButtons(page); + }); + + test('should render (custom) icons correctly', async ({ page }) => { + await renderIcons(page); + }); + + test('should render muted cards correctly', async ({ page }) => { + await renderMutedCards(page); + }); + + test('should render card headers correctly', async ({ page }) => { + await renderHeaders(page); + }); + + test('should render and remove dismissible cards', async ({ page }) => { + await renderAndDismissCard(page); + }); + + test.describe('Forms', () => { + test('should render form elements correctly', async ({ page }) => { + await renderFormElements(page); + }); + test('should disable forms on submit', async ({ page }) => { + await disableForm(page); + }); + test('should remove form card when canceled', async ({ page }) => { + await removeForm(page); + }); + }); + + test('should keep the content inside window boundaries', async ({ page }) => { + await checkContentInsideWindowBoundaries(page); + }); + + test('should parse markdown', async ({ page }) => { + await parseMarkdown(page); + }); + + test.describe('Prompt navigation', () => { + test('should navigate up to previous prompt', async ({ page }) => { + await navigatePromptsUp(page); + }); + test('should navigate down to next prompt', async ({ page }) => { + await navigatePromptsDown(page); + }); + test('should navigate down to current empty prompt', async ({ page }) => { + await navigatePromptsToEmpty(page); + }); + test.skip('should navigate up/down only if on first/last line', async ({ page }) => { + await navigatePromptsFirstLastLineCheck(page); + }); + + test('should stay on current prompt', async ({ page }) => { + await stayOnCurrentPrompt(page); + }); + + test.skip('should navigate back to current prompt', async ({ page }) => { + await navigateBackToCurrentPrompt(page); + }); + + test('should navigate back to current prompt with code attachment', async ({ page }) => { + await navigateBackToCurrentPromptWithCodeAttachment(page); + }); + }); + + test.describe('Feedback form', () => { + test('should render vote buttons', async ({ page }) => { + await renderVoteButtons(page); + }); + test('should render upvote results', async ({ page }) => { + await renderUpvoteResult(page); + }); + test('should render downvote results', async ({ page }) => { + await renderDownvoteResult(page); + }); + test('should render feedback form', async ({ page }) => { + await renderFeedbackForm(page); + }); + test('should cancel feedback form', async ({ page }) => { + await cancelFeedbackForm(page); + }); + test('should submit feedback form', async ({ page }) => { + await submitFeedbackForm(page); + }); + }); + test.describe('Dropdown list', () => { + test('should open and close dropdown', async ({ page }) => { + await openCloseDropdown(page); + }); + test('should select dropdown option', async ({ page }) => { + await selectDropdownOption(page); + }); + }); + + test('should display context command disabled text', async ({ page }) => { + await renderDisabledText(page); + }); +}); diff --git a/vendor/mynah-ui/ui-tests/package.json b/vendor/mynah-ui/ui-tests/package.json new file mode 100644 index 00000000..9ff01c67 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/package.json @@ -0,0 +1,50 @@ +{ + "name": "@amzn/mynah-website", + "version": "1.0.0", + "description": "", + "main": "webpack.media.config.js", + "scripts": { + "clean": "rm -rf build dist node_modules __results__", + "prepare": "webpack --config webpack.config.js --mode development", + "playwright:setup": "node ../scripts/setup-playwright.js", + "playwright:version": "node ../scripts/get-playwright-version.js", + "playwright:pre-test": "node ../scripts/pre-test-setup.js", + "e2e": "npm run playwright:pre-test && playwright test --workers=12", + "e2e:update": "npm run playwright:pre-test && playwright test --update-snapshots --workers=12", + "e2e:chromium": "npm run playwright:pre-test && playwright test --project=chromium --workers=12", + "e2e:webkit": "npm run playwright:pre-test && playwright test --project=webkit --workers=12", + "e2e:update:chromium": "npm run playwright:pre-test && playwright test --project=chromium --update-snapshots --workers=12", + "e2e:update:webkit": "npm run playwright:pre-test && playwright test --project=webkit --update-snapshots --workers=12", + "e2e:quick": "playwright test --workers=12", + "trace": "playwright show-trace" + }, + "dependencies": { + "@aws/mynah-ui": "file:../" + }, + "devDependencies": { + "@playwright/test": "^1.50.0", + "css-loader": "^6.6.0", + "html-webpack-plugin": "^5.5.0", + "playwright": "^1.50.0", + "raw-loader": "^4.0.2", + "sass": "^1.49.8", + "sass-loader": "^12.6.0", + "style-loader": "^3.3.1", + "ts-loader": "^9.4.4", + "ts-node": "^10.9.1", + "typescript": "^5.0.4", + "webpack": "^5.61.0", + "webpack-cli": "^4.7.2" + }, + "prettier": { + "printWidth": 160, + "trailingComma": "es5", + "tabWidth": 4, + "singleQuote": true, + "semi": true, + "bracketSpacing": true, + "arrowParens": "avoid", + "endOfLine": "lf" + }, + "author": "" +} diff --git a/vendor/mynah-ui/ui-tests/playwright.config.ts b/vendor/mynah-ui/ui-tests/playwright.config.ts new file mode 100644 index 00000000..8e0f5607 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/playwright.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from '@playwright/test'; +import { DEFAULT_VIEWPORT } from './__test__/helpers'; + +const isHeadless = process.env.HEADLESS !== 'false'; +const updateSnapshots = process.env.UPDATE_SNAPSHOTS === 'true'; + +export default defineConfig({ + testDir: './__test__', + testMatch: [ '**/?(*.)+(spec|test).[t]s' ], + testIgnore: [ '/node_modules/', 'dist', 'src' ], + timeout: 30000, + snapshotDir: './__snapshots__', + outputDir: './__results__/__reports__', + snapshotPathTemplate: '{snapshotDir}{/projectName}/{testName}/{arg}{ext}', + fullyParallel: true, + use: { + headless: isHeadless, + trace: 'retain-on-failure', // Capture trace only on failure + viewport: DEFAULT_VIEWPORT, // Enforce the default viewport + }, + expect: { + toMatchSnapshot: { + maxDiffPixelRatio: 0.01, + }, + }, + projects: [ + { + name: 'chromium', + use: { + browserName: 'chromium', + launchOptions: { + args: [ + '--disable-features=VizDisplayCompositor', + '--disable-dev-shm-usage', + '--no-sandbox', + ], + }, + } + }, + { name: 'webkit', use: { browserName: 'webkit' } }, + ], + updateSnapshots: updateSnapshots ? 'all' : 'missing', + reporter: [ + [ 'list' ], + [ 'junit', { outputFile: './__results__/__reports__/junit.xml' } ], + ], +}); diff --git a/vendor/mynah-ui/ui-tests/src/connector.ts b/vendor/mynah-ui/ui-tests/src/connector.ts new file mode 100644 index 00000000..0c47a0e9 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/connector.ts @@ -0,0 +1,35 @@ +import { ChatItem } from '@aws/mynah-ui'; +const STREAM_DELAY = 150; +const INITIAL_STREAM_DELAY = 300; + +export class Connector { + requestGenerativeAIAnswer = async ( + streamingChatItems: Array>, + onStreamUpdate: (chatItem: Partial) => boolean, + onStreamEnd: () => void + ): Promise => await new Promise((resolve, reject) => { + setTimeout(() => { + resolve(true); + let streamFillInterval: ReturnType; + const mdStream = streamingChatItems.map(i => i).reverse(); + const intervalTimingMultiplier = Math.floor(Math.random() * (2) + 1); + + const endStream = (): void => { + onStreamEnd(); + clearInterval(streamFillInterval); + }; + setTimeout(() => { + streamFillInterval = setInterval(() => { + if (mdStream.length > 0) { + const stopStream = onStreamUpdate(mdStream.pop() ?? {}); + if (stopStream) { + endStream(); + } + } else { + endStream(); + } + }, STREAM_DELAY * intervalTimingMultiplier); + }, INITIAL_STREAM_DELAY); + }, 150); + }); +} diff --git a/vendor/mynah-ui/ui-tests/src/defaults.ts b/vendor/mynah-ui/ui-tests/src/defaults.ts new file mode 100644 index 00000000..5cae7ca2 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/defaults.ts @@ -0,0 +1,63 @@ +import { + ChatItemType, + MynahUITabStoreTab, + QuickActionCommandGroup, + MynahIcons +} from '@aws/mynah-ui'; +import { Commands, mockFollowups, welcomeMessage } from './mocks/mock-data'; + +export const quickActionCommands: QuickActionCommandGroup[] = [ + { + commands: [ + { + command: Commands.HELP, + description: 'Show help text.', + }, + { + command: Commands.CLEAR, + description: 'Clear all chat.', + } + ], + }, +]; + +export const contextCommands: QuickActionCommandGroup[] = [ + { + groupName: 'Mention code', + commands: [ + { + command: '@workspace', + description: 'Reference all code in workspace.' + }, + { + command: '@file', + description: 'Reference the code in the current file.' + } + ] + } +]; + +export const defaultDataSet: Partial = { + store: { + tabTitle: 'Chat', + cancelButtonWhenLoading: true, + promptInputInfo: 'This is the **footer** of the panel.', + chatItems: [ + { + type: ChatItemType.ANSWER, + body: welcomeMessage, + messageId: 'welcome-message' + }, + mockFollowups + ], + quickActionCommands, + quickActionCommandsHeader: { + status: 'warning', + icon: MynahIcons.INFO, + title: 'Q Developer agentic capabilities', + description: 'You can now ask Q directly in the chat to generate code, documentation, and unit tests. You don\'t need to explicitly use /dev, /test, or /doc', + }, + contextCommands, + promptInputPlaceholder: 'Type something or "/" for quick action commands or @ for choosing context', + } +}; diff --git a/vendor/mynah-ui/ui-tests/src/file-types.d.ts b/vendor/mynah-ui/ui-tests/src/file-types.d.ts new file mode 100644 index 00000000..1b3aa2f3 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/file-types.d.ts @@ -0,0 +1,2 @@ +declare module '*.md'; +declare module '*.png'; diff --git a/vendor/mynah-ui/ui-tests/src/globals.d.ts b/vendor/mynah-ui/ui-tests/src/globals.d.ts new file mode 100644 index 00000000..5db20429 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/globals.d.ts @@ -0,0 +1,11 @@ +import { DetailedList, MynahUI } from '@aws/mynah-ui'; + +declare global { + interface Window { + mynahUI: MynahUI; + topBarOverlayController: { + update: (data: DetailedList) => void; + close: () => void; + }; + } +} diff --git a/vendor/mynah-ui/ui-tests/src/index.html b/vendor/mynah-ui/ui-tests/src/index.html new file mode 100644 index 00000000..b256ce3b --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/index.html @@ -0,0 +1,10 @@ + + + + + + + +
    + + diff --git a/vendor/mynah-ui/ui-tests/src/main.ts b/vendor/mynah-ui/ui-tests/src/main.ts new file mode 100644 index 00000000..ff7f363c --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/main.ts @@ -0,0 +1,219 @@ +/* eslint-disable @typescript-eslint/no-dynamic-delete */ +import { Connector } from './connector'; +import { + MynahUI, + ChatPrompt, + RelevancyVoteType, + ChatItemType, + FeedbackPayload, + ChatItemAction, + ChatItem, + generateUID, +} from '@aws/mynah-ui'; +import { defaultDataSet } from './defaults'; +import { Commands, mockFollowups, mockStreamParts } from './mocks/mock-data'; +import './styles/styles.scss'; + +export const createMynahUI = (): MynahUI => { + const connector = new Connector(); + let streamingMessageId: string | null; + + const mynahUI = new MynahUI({ + rootSelector: '#mynah-ui-wrapper', + defaults: { + store: { + ...(defaultDataSet.store) + } + }, + config: { + noMoreTabsTooltip: 'Tabs limit.', + autoFocus: true, + test: true, + texts: { + feedbackFormDescription: '_Feedback description. A link [GitHub](https://github.com/aws/mynah-ui/issues)._' + } + }, + tabs: { + 'tab-1': { + isSelected: true, + store: { + ...defaultDataSet.store + }, + }, + }, + onFocusStateChanged: (focusState: boolean) => { + // + }, + onTabBarButtonClick: (tabId: string, buttonId: string) => { + // + }, + onTabAdd: (tabId: string) => { + // + }, + onContextSelected: (contextItem) => { + return true; + }, + onBeforeTabRemove: (tabId: string): boolean => { + return (mynahUI.getAllTabs()[tabId].store?.loadingChat === false); + }, + onTabRemove: (tabId: string) => { + // + }, + onTabChange: (tabId: string) => { + // + }, + onSendFeedback: (tabId: string, feedbackPayload: FeedbackPayload) => { + // + }, + onShowMoreWebResultsClick: (tabId, messageId) => { + // + }, + onCopyCodeToClipboard: (tabId, messageId, code, type, referenceTrackerInformation, eventId, codeBlockIndex, totalCodeBlocks) => { + // + }, + onCodeInsertToCursorPosition: (tabId, messageId, code, type, referenceTrackerInformation, eventId, codeBlockIndex, totalCodeBlocks) => { + // + }, + onCodeBlockActionClicked: (tabId, messageId, actionId, data, code, type, referenceTrackerInformation, eventId, codeBlockIndex, totalCodeBlocks) => { + // + }, + onChatPrompt: (tabId: string, prompt: ChatPrompt) => { + if (tabId === 'tab-1') { + mynahUI.updateStore(tabId, { + tabCloseConfirmationMessage: `Working on "${prompt.prompt != null ? String(prompt.prompt) : ''}"`, + }); + } + onChatPrompt(tabId, prompt); + }, + onStopChatResponse: (tabId: string) => { + streamingMessageId = null; + mynahUI.updateStore(tabId, { + loadingChat: false + }); + }, + onFollowUpClicked: (tabId: string, messageId: string, followUp: ChatItemAction) => { + if (followUp.prompt != null || followUp.command != null) { + onChatPrompt(tabId, { + command: followUp.command, + prompt: followUp.prompt, + escapedPrompt: followUp.escapedPrompt ?? followUp.prompt, + }); + } + }, + onInBodyButtonClicked: (tabId: string, messageId: string, action) => { + // + }, + onVote: (tabId: string, messageId: string, vote: RelevancyVoteType) => { + // + }, + onFileClick: (tabId: string, filePath: string, deleted: boolean, messageId?: string) => { + mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER, + messageId: generateUID(), + body: `file ${String(filePath)} clicked`, + }); + }, + onFileActionClick: (tabId, messageId, filePath, actionName) => { + mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER, + messageId: generateUID(), + body: `file ${String(filePath)} action button ${String(actionName)} clicked`, + }); + }, + onCustomFormAction: (tabId, action) => { + // + }, + onChatItemEngagement: (tabId, messageId, engagement) => { + // + }, + onLinkClick: (tabId, messageId, link, mouseEvent) => { + // + }, + onSourceLinkClick: (tabId, messageId, link, mouseEvent) => { + // + }, + onInfoLinkClick: (tabId, link, mouseEvent) => { + // + }, + onDropDownOptionChange: (tabId: string, messageId: string, selectedOptions: any[]) => { + // + }, + }); + + const onChatPrompt = (tabId: string, prompt: ChatPrompt): void => { + if (prompt.command !== undefined && prompt.command.trim() !== '') { + switch (prompt.command) { + case Commands.HELP: + mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER, + messageId: generateUID(), + body: 'Help Text', + }); + break; + case Commands.CLEAR: + mynahUI.updateStore(tabId, { + chatItems: [], + }); + break; + default: + mynahUI.addChatItem(tabId, { + type: ChatItemType.PROMPT, + messageId: generateUID(), + body: `**${(prompt.command != null ? String(prompt.command) : '').replace('/', '')}**\n${(prompt.escapedPrompt != null ? String(prompt.escapedPrompt) : '')}`, + }); + getGenerativeAIAnswer(tabId); + break; + } + } else { + mynahUI.addChatItem(tabId, { + type: ChatItemType.PROMPT, + messageId: generateUID(), + body: `${String(prompt.escapedPrompt)}`, + }); + getGenerativeAIAnswer(tabId); + } + }; + + const getGenerativeAIAnswer = (tabId: string, optionalParts?: Array>): void => { + const messageId = new Date().getTime().toString(); + mynahUI.updateStore(tabId, { + loadingChat: true, + promptInputDisabledState: true, + }); + connector + .requestGenerativeAIAnswer( + optionalParts ?? mockStreamParts, + (chatItem: Partial) => { + if (streamingMessageId != null) { + mynahUI.updateChatAnswerWithMessageId(tabId, streamingMessageId, chatItem); + return false; + } + return true; + }, + () => { + streamingMessageId = null; + setTimeout(() => { + mynahUI.addChatItem(tabId, mockFollowups); + mynahUI.updateStore(tabId, { + loadingChat: false, + promptInputDisabledState: false, + }); + mynahUI.endMessageStream(tabId, messageId) as Record; + }, 200); + } + ) + .then(() => { + streamingMessageId = messageId; + mynahUI.addChatItem(tabId, { + type: ChatItemType.ANSWER_STREAM, + body: '', + canBeVoted: true, + messageId: streamingMessageId, + }); + }); + }; + + return mynahUI; +}; + +window.mynahUI = createMynahUI(); diff --git a/vendor/mynah-ui/ui-tests/src/mocks/mock-data.ts b/vendor/mynah-ui/ui-tests/src/mocks/mock-data.ts new file mode 100644 index 00000000..144b4d50 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/mocks/mock-data.ts @@ -0,0 +1,76 @@ +import { ChatItem, ChatItemType, SourceLink } from '@aws/mynah-ui'; +import md0 from './stream-0.md'; +import md1 from './stream-1.md'; +import md2 from './stream-2.md'; +import md3 from './stream-3.md'; +import md4 from './stream-4.md'; + +export enum Commands { + HELP = '/help', + CLEAR = '/clear', +} + +export const welcomeMessage = `### MynahUI +Hello world!.`; + +export const mockFollowups: ChatItem = { + type: ChatItemType.ANSWER, + messageId: 'mynah-ui-test-followup', + followUp: { + text: 'Mock followups', + options: [ + { + command: 'followup-1', + pillText: 'Followup 1', + prompt: 'Followup 1 prompt', + }, + { + command: 'followup-2', + pillText: 'Followup 2', + description: 'Followup 1 description', + prompt: 'Followup 2 prompt', + }, + ], + }, +}; + +export const mockSources = [ + { + url: 'https://github.com/aws/mynah-ui', + title: 'Mock Source 1', + body: md0 as string, + }, + { + url: 'https://github.com/aws/mynah-ui/blob/main/docs/STARTUP.md', + title: 'Mock Source 2', + body: md1 as string, + }, + { + url: 'https://github.com/aws/mynah-ui/blob/main/docs/USAGE.md', + title: 'Mock Source 3', + body: md2 as string, + }, +] as SourceLink[]; + +export const mockStreamParts: Array> = [ + { body: `${md0 as string}` }, + { body: `${md1 as string}` }, + { body: `${md2 as string}` }, + { body: `${md3 as string}` }, + { body: `${md4 as string}` }, + { + relatedContent: { + content: mockSources, + title: 'Sources', + }, + codeReference: [ + { + recommendationContentSpan: { + start: 762, + end: 777, + }, + information: 'Mock code reference **`MynahUI`**.', + } + ], + }, +]; diff --git a/vendor/mynah-ui/ui-tests/src/mocks/stream-0.md b/vendor/mynah-ui/ui-tests/src/mocks/stream-0.md new file mode 100644 index 00000000..9f7fb0ff --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/mocks/stream-0.md @@ -0,0 +1,6 @@ +## Perque adacto fugio + +Invectae moribundo et eripiet sine, adventu tolli *liquidas* satiatur Perseus; +**locus**, nato! More dei timeas dextra Granico neu corpus simul *operique*! +Fecit mea, sua, hoc vias proles pallebant illa est populosque festa manetque +clamato nescisse. \ No newline at end of file diff --git a/vendor/mynah-ui/ui-tests/src/mocks/stream-1.md b/vendor/mynah-ui/ui-tests/src/mocks/stream-1.md new file mode 100644 index 00000000..21da933e --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/mocks/stream-1.md @@ -0,0 +1,10 @@ +## Perque adacto fugio + +Invectae moribundo et eripiet sine, adventu tolli *liquidas* satiatur Perseus; +**locus**, nato! More dei timeas dextra Granico neu corpus simul *operique*! +Fecit mea, sua, hoc vias proles pallebant illa est populosque festa manetque +clamato nescisse. + +1. In corpora officio +2. Est inde noxae erat qui redditque +3. Cura vagae novum emisit flamina litore caede \ No newline at end of file diff --git a/vendor/mynah-ui/ui-tests/src/mocks/stream-2.md b/vendor/mynah-ui/ui-tests/src/mocks/stream-2.md new file mode 100644 index 00000000..7bd1990c --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/mocks/stream-2.md @@ -0,0 +1,17 @@ +## Perque adacto fugio + +Invectae moribundo et eripiet sine, adventu tolli *liquidas* satiatur Perseus; +**locus**, nato! More dei timeas dextra Granico neu corpus simul *operique*! +Fecit mea, sua, hoc vias proles pallebant illa est populosque festa manetque +clamato nescisse. + +1. In corpora officio +2. Est inde noxae erat qui redditque +3. Cura vagae novum emisit flamina litore caede + +## Sibi nomen templis sentit indicere linguae manu + +Et ferro sic lentosque tamen; quis tamen adultera tenet modo. Vox Pagasaea +parientem patre sonum coniuge maculat Diana, iussit: misi lacertis. Coniuge ne +satis **triumphos Cinyreius** tertia. Patrios hoc `velocior` relinque ex totque +potes nuntia me pascitur iacet iners pararis septem. \ No newline at end of file diff --git a/vendor/mynah-ui/ui-tests/src/mocks/stream-3.md b/vendor/mynah-ui/ui-tests/src/mocks/stream-3.md new file mode 100644 index 00000000..b9722350 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/mocks/stream-3.md @@ -0,0 +1,27 @@ +## Perque adacto fugio + +Invectae moribundo et eripiet sine, adventu tolli *liquidas* satiatur Perseus; +**locus**, nato! More dei timeas dextra Granico neu corpus simul *operique*! +Fecit mea, sua, hoc vias proles pallebant illa est populosque festa manetque +clamato nescisse. + +1. In corpora officio +2. Est inde noxae erat qui redditque +3. Cura vagae novum emisit flamina litore caede + +## Sibi nomen templis sentit indicere linguae manu + +Et ferro sic lentosque tamen; quis tamen adultera tenet modo. Vox Pagasaea +parientem patre sonum coniuge maculat Diana, iussit: misi lacertis. Coniuge ne +satis **triumphos Cinyreius** tertia. Patrios hoc `velocior` relinque ex totque +potes nuntia me pascitur iacet iners pararis septem. + +```typescript +if (tertia != null) { + velocior = sli; + relinque += 5; + pararis.san_fsb_bankruptcy -= relinque + 4; +} else { + septem(relinque, 2); +} +``` diff --git a/vendor/mynah-ui/ui-tests/src/mocks/stream-4.md b/vendor/mynah-ui/ui-tests/src/mocks/stream-4.md new file mode 100644 index 00000000..46cd7584 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/mocks/stream-4.md @@ -0,0 +1,31 @@ +## Perque adacto fugio + +Invectae moribundo et eripiet sine, adventu tolli *liquidas* satiatur Perseus; +**locus**, nato! More dei timeas dextra Granico neu corpus simul *operique*! +Fecit mea, sua, hoc vias proles pallebant illa est populosque festa manetque +clamato nescisse. + +1. In corpora officio +2. Est inde noxae erat qui redditque +3. Cura vagae novum emisit flamina litore caede + +## Sibi nomen templis sentit indicere linguae manu + +Et ferro sic lentosque tamen; quis tamen adultera tenet modo. Vox Pagasaea +parientem patre sonum coniuge maculat Diana, iussit: misi lacertis. Coniuge ne +satis **triumphos Cinyreius** tertia. Patrios hoc `velocior` relinque ex totque +potes nuntia me pascitur iacet iners pararis septem. + +```typescript +if (tertia != null) { + velocior = sli; + relinque += 5; + pararis.san_fsb_bankruptcy -= relinque + 4; +} else { + septem(relinque, 2); +} +``` + +Discedit ruit repulsa quoque suam captare quoque genitore certa! Nullos ad fieri +Achille amor, vocant Neptunus Iunonem media deponit alimentaque unus. Summaque +quae pro habeo virgo ad nuper sustulit, perque claudit dracones. \ No newline at end of file diff --git a/vendor/mynah-ui/ui-tests/src/styles/roboto-bold.ttf b/vendor/mynah-ui/ui-tests/src/styles/roboto-bold.ttf new file mode 100644 index 00000000..e64db796 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/src/styles/roboto-bold.ttf differ diff --git a/vendor/mynah-ui/ui-tests/src/styles/roboto.ttf b/vendor/mynah-ui/ui-tests/src/styles/roboto.ttf new file mode 100644 index 00000000..2d116d92 Binary files /dev/null and b/vendor/mynah-ui/ui-tests/src/styles/roboto.ttf differ diff --git a/vendor/mynah-ui/ui-tests/src/styles/styles.scss b/vendor/mynah-ui/ui-tests/src/styles/styles.scss new file mode 100644 index 00000000..dff8e3d0 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/styles/styles.scss @@ -0,0 +1,47 @@ +@import 'theme'; + +html, +body { + font-size: var(--mynah-font-size-medium) !important; + width: 100vw; + height: 100vh; + margin: 0; + padding: 0; + display: block; + overflow: hidden; + background-color: var(--mynah-color-bg); + color: var(--mynah-color-text-default); + line-height: var(--mynah-line-height) !important; + transform: translateZ(0); + filter: contrast(1); +} +* { + font-stretch: 100%; + letter-spacing: normal; + text-rendering: optimizeSpeed; + -webkit-font-smoothing: antialiased; +} + +#mynah-ui-wrapper { + max-width: var(--mynah-max-width); + background-color: var(--mynah-color-bg); + border: none; + position: relative; + display: flex; + flex-flow: row nowrap; + z-index: var(--mynah-z-1); + flex: 1; + height: 100%; + width: 100%; + box-sizing: border-box; + overflow: hidden; + margin: 0 auto; +} + +.mynah-chat-prompt-input { + caret-color: rgba(0, 0, 0, 0) !important; +} + +.more-content-indicator { + opacity: 0 !important; +} diff --git a/vendor/mynah-ui/ui-tests/src/styles/theme.scss b/vendor/mynah-ui/ui-tests/src/styles/theme.scss new file mode 100644 index 00000000..9789f88d --- /dev/null +++ b/vendor/mynah-ui/ui-tests/src/styles/theme.scss @@ -0,0 +1,131 @@ +@font-face { + font-family: 'roboto'; + font-weight: 100; + src: url('roboto.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 200; + src: url('roboto.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 300; + src: url('roboto.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 400; + src: url('roboto.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: lighter; + src: url('roboto.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: normal; + src: url('roboto.ttf') format('ttf'); +} + +@font-face { + font-family: 'roboto'; + font-weight: bold; + src: url('roboto-bold.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: bolder; + src: url('roboto-bold.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 500; + src: url('roboto-bold.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 600; + src: url('roboto-bold.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 700; + src: url('roboto-bold.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 800; + src: url('roboto-bold.ttf') format('ttf'); +} +@font-face { + font-family: 'roboto'; + font-weight: 900; + src: url('roboto-bold.ttf') format('ttf'); +} +html:root { + font-size: 13px !important; + --mynah-max-width: 100vw; + --mynah-sizing-base: 3px; + --mynah-chat-wrapper-spacing: var(--mynah-sizing-6); + --mynah-font-family: 'roboto', arial; + --mynah-syntax-code-font-family: monospace; + --mynah-color-text-default: #517185; + --mynah-color-text-alternate: #ffffff; + --mynah-color-text-strong: #296689; + --mynah-color-text-weak: #a9bed0; + --mynah-color-text-link: #006ab1; + --mynah-color-text-input: #517185; + --mynah-color-light: rgba(0, 0, 0, 0.05); + --mynah-color-highlight: rgba(255, 180, 0, 0.1); + --mynah-color-highlight-text: #886411; + --mynah-color-border-default: rgba(119, 157, 182, 0.15); + --mynah-button-border-width: 1px; + --mynah-border-width: 1px; + --mynah-color-syntax-variable: #7e009e; + --mynah-color-syntax-function: #4dc9ff; + --mynah-color-syntax-operator: #e60086; + --mynah-color-syntax-attr-value: #007acc; + --mynah-color-syntax-attr: #dc0450; + --mynah-color-syntax-property: #00d1e0; + --mynah-color-syntax-comment: #0c923f; + --mynah-color-syntax-code: #0051a8; + --mynah-color-syntax-bg: rgba(40, 120, 200, 0.2); + --mynah-color-status-info: #28a7e6; + --mynah-color-status-success: #36e281; + --mynah-color-status-warning: #eca14b; + --mynah-color-status-error: #e60063; + --mynah-color-bg: #fafbfc; + --mynah-color-tab-active: #ffffff; + --mynah-color-toggle: #fafbfc; + --mynah-color-toggle-reverse: rgba(0, 0, 0, 0.5); + --mynah-color-button: #1e9ddc; + --mynah-color-button-reverse: #ffffff; + --mynah-color-alternate: #5f6a79; + --mynah-color-alternate-reverse: #ffffff; + --mynah-card-bg: #ffffff; + --mynah-card-bg-alternate: #6a91af; + --mynah-shadow-button: 0 5px 10px -10px rgba(0, 0, 0, 0.25); + --mynah-shadow-card: 0 10px 20px -15px rgba(0, 0, 0, 0.25); + --mynah-shadow-overlay: 0 7px 27px -12px rgba(0, 0, 0, 0.35); + --mynah-syntax-code-font-size: 13px; + --mynah-syntax-code-line-height: 15px; + --mynah-font-size-xxsmall: 8px; + --mynah-font-size-xsmall: 10px; + --mynah-font-size-small: 12px; + --mynah-font-size-medium: 13px; + --mynah-font-size-large: 15px; + --mynah-line-height: 15px; + --mynah-card-radius: var(--mynah-sizing-2); + --mynah-card-radius-corner: var(--mynah-sizing-1); + --mynah-button-radius: var(--mynah-sizing-1); + --mynah-input-radius: var(--mynah-sizing-1); + --mynah-bottom-panel-transition: all 450ms cubic-bezier(0.25, 1, 0, 1); + --mynah-very-short-transition: all 200ms cubic-bezier(0.25, 1, 0, 1); + --mynah-very-long-transition: all 850ms cubic-bezier(0.25, 1, 0, 1); + --mynah-short-transition: all 250ms cubic-bezier(0.85, 0.15, 0, 1); + --mynah-short-transition-rev: all 280ms cubic-bezier(0.35, 1, 0, 1); + --mynah-short-transition-rev-opacity: opacity 350ms cubic-bezier(0.35, 1, 0, 1); + --mynah-text-flow-transition: all 400ms cubic-bezier(0.35, 1.2, 0, 1), transform 400ms cubic-bezier(0.2, 1.05, 0, 1); +} diff --git a/vendor/mynah-ui/ui-tests/tsconfig.json b/vendor/mynah-ui/ui-tests/tsconfig.json new file mode 100644 index 00000000..0dccf5ee --- /dev/null +++ b/vendor/mynah-ui/ui-tests/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "commonjs", + "target": "ES2019", + "lib": ["ES2020", "es5", "es6", "dom"], + "outDir": "out", + "sourceMap": true, + "strict": true, + "resolveJsonModule": true, + "typeRoots": ["./node_modules/@types"], + "strictPropertyInitialization": false, + }, +} diff --git a/vendor/mynah-ui/ui-tests/webpack.config.js b/vendor/mynah-ui/ui-tests/webpack.config.js new file mode 100644 index 00000000..887fc2e2 --- /dev/null +++ b/vendor/mynah-ui/ui-tests/webpack.config.js @@ -0,0 +1,58 @@ +'use strict'; + +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +/**@type {import('webpack').Configuration}*/ +const config = { + target: 'web', + entry: './src/main.ts', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + library: 'mynahWeb', + libraryTarget: 'var', + devtoolModuleFilenameTemplate: '../[resource-path]', + }, + plugins: [ + new HtmlWebpackPlugin({ + template: 'src/index.html', + }), + ], + devtool: 'source-map', + resolve: { + extensions: ['.ts', '.js'], + }, + experiments: { asyncWebAssembly: true }, + module: { + rules: [ + { test: /\.md$/, use: ['raw-loader'] }, + { + test: /\.scss$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + mode: 'icss', // Enable ICSS (Interoperable CSS) + }, + }, + }, + 'sass-loader', + ], + }, + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader', + }, + ], + }, + ], + }, +}; +module.exports = config; diff --git a/vendor/mynah-ui/webpack.config.js b/vendor/mynah-ui/webpack.config.js new file mode 100644 index 00000000..50336efd --- /dev/null +++ b/vendor/mynah-ui/webpack.config.js @@ -0,0 +1,66 @@ +//@ts-check + +'use strict'; + +const path = require('path'); + +/**@type {import('webpack').Configuration}*/ +const config = { + target: 'web', + entry: './src/main.ts', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'main.js', + library: 'MynahUI', + libraryTarget: 'umd', + devtoolModuleFilenameTemplate: '../[resource-path]', + }, + devtool: 'source-map', + externals: { + vscode: 'commonjs vscode', + }, + resolve: { + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.scss$/, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + importLoaders: 1, + modules: { + mode: 'icss', // Enable ICSS (Interoperable CSS) + }, + }, + }, + 'sass-loader', + ], + }, + { + test: /\.ts$/, + exclude: [/node_modules/, /.\/example/, /.\/ui-tests/, /.\/example-react/], + use: [ + { + loader: 'ts-loader', + }, + ], + }, + { + test: /\.svg$/, + use: [ + { + loader: 'svg-url-loader', + options: { + encoding: 'base64', + }, + }, + ], + }, + ], + }, +}; +module.exports = config; diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore index 48f1c9c4..f91f6496 100644 --- a/vscode-extension/.vscodeignore +++ b/vscode-extension/.vscodeignore @@ -3,5 +3,9 @@ src/** .gitignore tsconfig.json +webpack.config.js **/*.map **/*.ts +../vendor/** +node_modules/** +test-workspace/** diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json index 730919d5..16722a9d 100644 --- a/vscode-extension/package-lock.json +++ b/vscode-extension/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@agentclientprotocol/sdk": "^0.5.1", - "@aws/mynah-ui": "^4.38.0", + "@aws/mynah-ui": "file:../vendor/mynah-ui", "@types/uuid": "^10.0.0", "uuid": "^13.0.0" }, @@ -33,19 +33,9 @@ "vscode": "^1.93.0" } }, - "node_modules/@agentclientprotocol/sdk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.5.1.tgz", - "integrity": "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg==", - "license": "Apache-2.0", - "dependencies": { - "zod": "^3.0.0" - } - }, - "node_modules/@aws/mynah-ui": { + "../vendor/mynah-ui": { + "name": "@aws/mynah-ui", "version": "4.38.0", - "resolved": "https://registry.npmjs.org/@aws/mynah-ui/-/mynah-ui-4.38.0.tgz", - "integrity": "sha512-c/+lyTfKm+6FB1GSxTqwLK0+YeQ4HJKaBPitAcH53hf+CaT9AUPz3ER/8FhmjExiZr4AWLfibEVQq9Of174PNw==", "hasInstallScript": true, "license": "Apache License 2.0", "dependencies": { @@ -56,6 +46,48 @@ "sanitize-html": "^2.12.1", "unescape-html": "^1.1.0" }, + "devDependencies": { + "@babel/core": "^7.23.5", + "@types/eslint": "^8.44.3", + "@types/eslint-scope": "3.7.0", + "@types/estree": "0.0.49", + "@types/glob": "7.1.3", + "@types/jest": "^29.5.5", + "@types/json-schema": "7.0.7", + "@types/minimatch": "^5.1.2", + "@types/node": "^24.0.0", + "@types/sanitize-html": "^2.11.0", + "@typescript-eslint/eslint-plugin": "^5.34.0", + "@typescript-eslint/parser": "^5.62.0", + "babel-jest": "^29.7.0", + "core-js": "^3.33.3", + "css-loader": "6.6.0", + "eslint": "^8.22.0", + "eslint-config-standard-with-typescript": "22.0.0", + "eslint-plugin-header": "^3.1.1", + "eslint-plugin-import": "2.26.0", + "eslint-plugin-n": "15.2.5", + "eslint-plugin-no-null": "^1.0.2", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-promise": "6.0.0", + "husky": "^9.1.6", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-svg-transformer": "^1.0.0", + "npm-run-all": "^4.1.5", + "prettier": "^3.0.3", + "sass": "1.49.8", + "sass-loader": "12.6.0", + "style-loader": "3.3.1", + "svg-url-loader": "^8.0.0", + "ts-jest": "^29.1.1", + "ts-loader": "^9.4.4", + "ts-node": "^10.9.1", + "typedoc": "^0.25.13", + "typescript": "^5.1.6", + "webpack": "5.94.0", + "webpack-cli": "4.7.2" + }, "peerDependencies": { "escape-html": "^1.0.3", "highlight.js": "^11.11.0", @@ -65,6 +97,19 @@ "unescape-html": "^1.1.0" } }, + "node_modules/@agentclientprotocol/sdk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.5.1.tgz", + "integrity": "sha512-9bq2TgjhLBSUSC5jE04MEe+Hqw8YePzKghhYZ9QcjOyonY3q2oJfX6GoSO83hURpEnsqEPIrex6VZN3+61fBJg==", + "license": "Apache-2.0", + "dependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@aws/mynah-ui": { + "resolved": "../vendor/mynah-ui", + "link": true + }, "node_modules/@bcoe/v8-coverage": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", @@ -1638,15 +1683,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/diff": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", @@ -1683,61 +1719,6 @@ "node": ">=6.0.0" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -1773,18 +1754,6 @@ "node": ">=10.13.0" } }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/envinfo": { "version": "7.20.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.20.0.tgz", @@ -1815,16 +1784,11 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2403,15 +2367,6 @@ "he": "bin/he" } }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -2419,25 +2374,6 @@ "dev": true, "license": "MIT" }, - "node_modules/htmlparser2": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", - "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1", - "entities": "^4.4.0" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -2867,12 +2803,6 @@ "setimmediate": "^1.0.5" } }, - "node_modules/just-clone": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz", - "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==", - "license": "MIT" - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2994,18 +2924,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/marked": { - "version": "14.1.4", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.1.4.tgz", - "integrity": "sha512-vkVZ8ONmUdPnjCKc5uTRvmkRbx4EAi2OkTOXmfTDhZz3OFqMNBM1oTTWwTr4HY4uAEojhzPf+Fy8F1DWa3Sndg==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -3214,6 +3132,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -3506,12 +3425,6 @@ "node": ">=6" } }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", - "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3580,6 +3493,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3668,6 +3582,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4047,29 +3962,6 @@ ], "license": "MIT" }, - "node_modules/sanitize-html": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", - "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", - "license": "MIT", - "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, - "node_modules/sanitize-html/node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -4230,6 +4122,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4605,12 +4498,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unescape-html": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unescape-html/-/unescape-html-1.1.0.tgz", - "integrity": "sha512-O9/yBNqIkArjS597iHez5hAaAdn7b8/230SX8IncgXAX5tWI9XlEQYaz6Qbou0Sloa9n6lx9G5s6hg5qhJyzGg==", - "license": "MIT" - }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", diff --git a/vscode-extension/package.json b/vscode-extension/package.json index 6201219d..d83c6fda 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -3,7 +3,7 @@ "displayName": "Symposium", "description": "Collaborative AI built collaboratively", "version": "0.0.1", - "publisher": "symposium", + "publisher": "symposium-dev", "repository": { "type": "git", "url": "https://github.com/symposium/symposium.git" @@ -109,6 +109,21 @@ ], "default": "", "description": "Log level for the symposium-acp-agent process. Logs appear in the Symposium output channel. Leave empty to disable." + }, + "symposium.requireModifierToSend": { + "type": "boolean", + "default": false, + "description": "When enabled, Enter adds a newline and Shift+Enter (or Cmd+Enter on Mac) sends the prompt. When disabled (default), Enter sends and Shift+Enter adds a newline." + }, + "symposium.enableSparkle": { + "type": "boolean", + "default": true, + "description": "Enable the Sparkle component for collaborative AI identity and memory." + }, + "symposium.enableCrateResearcher": { + "type": "boolean", + "default": true, + "description": "Enable the Rust Crate Researcher component for Rust crate documentation lookup." } } }, @@ -172,7 +187,7 @@ }, "dependencies": { "@agentclientprotocol/sdk": "^0.5.1", - "@aws/mynah-ui": "^4.38.0", + "@aws/mynah-ui": "file:../vendor/mynah-ui", "@types/uuid": "^10.0.0", "uuid": "^13.0.0" } diff --git a/vscode-extension/src/acpAgentActor.ts b/vscode-extension/src/acpAgentActor.ts index 4e646e05..35668e58 100644 --- a/vscode-extension/src/acpAgentActor.ts +++ b/vscode-extension/src/acpAgentActor.ts @@ -255,6 +255,14 @@ export class AcpAgentActor { conductorArgs.push("--log", agentLogLevel); } + // Add component disable flags if components are disabled + if (!config.enableSparkle) { + conductorArgs.push("--no-sparkle"); + } + if (!config.enableCrateResearcher) { + conductorArgs.push("--no-crate-researcher"); + } + conductorArgs.push("--", agentCmd, ...agentArgs); logger.important("agent", "Spawning ACP agent", { @@ -387,6 +395,25 @@ export class AcpAgentActor { this.callbacks.onAgentComplete(agentSessionId); } + /** + * Cancel an ongoing prompt turn for a session. + * + * Sends a session/cancel notification to the agent. The agent should: + * - Stop all language model requests as soon as possible + * - Abort all tool call invocations in progress + * - Respond to the original prompt with stopReason: "cancelled" + * + * @param agentSessionId - Agent session identifier + */ + async cancelSession(agentSessionId: string): Promise { + if (!this.connection) { + throw new Error("ACP connection not initialized"); + } + + logger.debug("agent", "Cancelling session", { agentSessionId }); + await this.connection.cancel({ sessionId: agentSessionId }); + } + /** * Cleanup - kill the agent process */ diff --git a/vscode-extension/src/agentConfiguration.ts b/vscode-extension/src/agentConfiguration.ts index bb92d7e0..6d5400c4 100644 --- a/vscode-extension/src/agentConfiguration.ts +++ b/vscode-extension/src/agentConfiguration.ts @@ -11,13 +11,21 @@ export class AgentConfiguration { constructor( public readonly agentName: string, public readonly workspaceFolder: vscode.WorkspaceFolder, + public readonly enableSparkle: boolean = true, + public readonly enableCrateResearcher: boolean = true, ) {} /** * Get a unique key for this configuration */ key(): string { - return `${this.agentName}:${this.workspaceFolder.uri.fsPath}`; + const components = [ + this.enableSparkle ? "sparkle" : "", + this.enableCrateResearcher ? "crate-researcher" : "", + ] + .filter((c) => c) + .join("+"); + return `${this.agentName}:${this.workspaceFolder.uri.fsPath}:${components}`; } /** @@ -31,7 +39,15 @@ export class AgentConfiguration { * Get a human-readable description */ describe(): string { - return this.agentName; + const components = [ + this.enableSparkle ? "Sparkle" : null, + this.enableCrateResearcher ? "Rust Crate Researcher" : null, + ].filter((c) => c !== null); + + if (components.length === 0) { + return this.agentName; + } + return `${this.agentName} + ${components.join(" + ")}`; } /** @@ -46,6 +62,13 @@ export class AgentConfiguration { // Get current agent const currentAgentName = config.get("currentAgent", "Claude Code"); + // Get component settings + const enableSparkle = config.get("enableSparkle", true); + const enableCrateResearcher = config.get( + "enableCrateResearcher", + true, + ); + // Determine workspace folder let folder = workspaceFolder; if (!folder) { @@ -66,6 +89,11 @@ export class AgentConfiguration { } } - return new AgentConfiguration(currentAgentName, folder); + return new AgentConfiguration( + currentAgentName, + folder, + enableSparkle, + enableCrateResearcher, + ); } } diff --git a/vscode-extension/src/chatViewProvider.ts b/vscode-extension/src/chatViewProvider.ts index e144cc3a..4b153712 100644 --- a/vscode-extension/src/chatViewProvider.ts +++ b/vscode-extension/src/chatViewProvider.ts @@ -59,6 +59,9 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { { resolve: (tabId: string | undefined) => void } > = new Map(); + // Track tabs with active prompts in progress (for cancellation) + #tabsWithActivePrompt: Set = new Set(); + #extensionContext: vscode.ExtensionContext; constructor( @@ -124,6 +127,9 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { onAgentComplete: (agentSessionId) => { const tabId = this.#agentSessionToTab.get(agentSessionId); if (tabId) { + // Clear the active prompt flag now that the turn is complete + this.#tabsWithActivePrompt.delete(tabId); + this.#sendToWebview({ type: "agent-complete", tabId, @@ -627,6 +633,22 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { return; } + // Cancel any in-progress prompt before sending a new one + if (this.#tabsWithActivePrompt.has(message.tabId)) { + logger.debug("agent", "Cancelling previous prompt before new one", { + tabId: message.tabId, + agentSessionId, + }); + try { + await tabActor.cancelSession(agentSessionId); + } catch (err) { + logger.error("agent", "Failed to cancel previous prompt", { + error: err, + }); + // Continue anyway - the new prompt should still work + } + } + // Build content blocks for the prompt const contentBlocks: acp.ContentBlock[] = []; @@ -737,10 +759,15 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { contentBlockCount: contentBlocks.length, }); + // Mark this tab as having an active prompt + this.#tabsWithActivePrompt.add(message.tabId); + // Send prompt to agent (responses come via callbacks) try { await tabActor.sendPrompt(agentSessionId, contentBlocks); } catch (err: any) { + // Clear active prompt flag on error + this.#tabsWithActivePrompt.delete(message.tabId); logger.error("agent", "Failed to send prompt", { error: err }); this.#sendToWebview({ type: "agent-error", @@ -993,6 +1020,13 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { vscode.Uri.joinPath(this.#extensionUri, "out", "webview.js"), ); + // Get the requireModifierToSend setting + const config = vscode.workspace.getConfiguration("symposium"); + const requireModifierToSend = config.get( + "requireModifierToSend", + false, + ); + return ` @@ -1016,6 +1050,8 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { @@ -1175,6 +1211,14 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { this.#testResponseCapture.delete(tabId); } + /** + * Check if a tab has an active prompt in progress. + * Used for testing cancellation behavior. + */ + public hasActivePrompt(tabId: string): boolean { + return this.#tabsWithActivePrompt.has(tabId); + } + async #handleWebviewMessage(message: any): Promise { // This is the same logic from resolveWebviewView's onDidReceiveMessage // We'll need to refactor to share this code @@ -1254,14 +1298,35 @@ export class ChatViewProvider implements vscode.WebviewViewProvider { return; } + // Cancel any in-progress prompt before sending a new one + if (this.#tabsWithActivePrompt.has(message.tabId)) { + logger.debug("agent", "Cancelling previous prompt before new one", { + tabId: message.tabId, + agentSessionId, + }); + try { + await tabActor.cancelSession(agentSessionId); + } catch (err) { + logger.error("agent", "Failed to cancel previous prompt", { + error: err, + }); + // Continue anyway - the new prompt should still work + } + } + logger.debug("agent", "Sending prompt to agent", { tabId: message.tabId, agentSessionId, }); + // Mark this tab as having an active prompt + this.#tabsWithActivePrompt.add(message.tabId); + // Send prompt to agent (responses come via callbacks) await tabActor.sendPrompt(agentSessionId, message.prompt); } catch (err: any) { + // Clear active prompt flag on error + this.#tabsWithActivePrompt.delete(message.tabId); logger.error("agent", "Failed to send prompt", { error: err }); this.#sendToWebview({ type: "agent-error", diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index f81ba2fe..6eb80bd9 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -184,6 +184,15 @@ export function activate(context: vscode.ExtensionContext) { }, ), ); + + context.subscriptions.push( + vscode.commands.registerCommand( + "symposium.test.hasActivePrompt", + (tabId: string) => { + return chatProvider.hasActivePrompt(tabId); + }, + ), + ); } export function deactivate() {} diff --git a/vscode-extension/src/settingsViewProvider.ts b/vscode-extension/src/settingsViewProvider.ts index abe21eb1..9d4eadfc 100644 --- a/vscode-extension/src/settingsViewProvider.ts +++ b/vscode-extension/src/settingsViewProvider.ts @@ -59,10 +59,7 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { // Send updated configuration to refresh the UI this.#sendConfiguration(); break; - case "toggle-component": - // Toggle component enabled/disabled - await this.#toggleComponent(message.componentName); - break; + case "toggle-bypass-permissions": // Toggle bypass permissions for an agent await this.#toggleBypassPermissions(message.agentName); @@ -74,27 +71,18 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { "symposium", ); break; + case "toggle-require-modifier-to-send": + // Toggle the requireModifierToSend setting + await this.#toggleRequireModifierToSend(); + break; + case "toggle-component": + // Toggle a component enabled/disabled + await this.#toggleComponentSetting(message.componentId); + break; } }); } - async #toggleComponent(componentName: string) { - const config = vscode.workspace.getConfiguration("symposium"); - const components = config.get< - Record - >("components", {}); - - if (components[componentName]) { - components[componentName].disabled = !components[componentName].disabled; - await config.update( - "components", - components, - vscode.ConfigurationTarget.Global, - ); - this.#sendConfiguration(); - } - } - async #toggleBypassPermissions(agentName: string) { const config = vscode.workspace.getConfiguration("symposium"); const agents = config.get>("agents", {}); @@ -110,6 +98,30 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { } } + async #toggleRequireModifierToSend() { + const config = vscode.workspace.getConfiguration("symposium"); + const currentValue = config.get("requireModifierToSend", false); + await config.update( + "requireModifierToSend", + !currentValue, + vscode.ConfigurationTarget.Global, + ); + this.#sendConfiguration(); + } + + async #toggleComponentSetting(componentId: string) { + const config = vscode.workspace.getConfiguration("symposium"); + const settingName = + componentId === "sparkle" ? "enableSparkle" : "enableCrateResearcher"; + const currentValue = config.get(settingName, true); + await config.update( + settingName, + !currentValue, + vscode.ConfigurationTarget.Global, + ); + this.#sendConfiguration(); + } + #sendConfiguration() { if (!this.#view) { return; @@ -118,13 +130,23 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { const config = vscode.workspace.getConfiguration("symposium"); const agents = config.get("agents", {}); const currentAgent = config.get("currentAgent", ""); - const components = config.get("components", {}); + const requireModifierToSend = config.get( + "requireModifierToSend", + false, + ); + const enableSparkle = config.get("enableSparkle", true); + const enableCrateResearcher = config.get( + "enableCrateResearcher", + true, + ); this.#view.webview.postMessage({ type: "config", agents, currentAgent, - components, + requireModifierToSend, + enableSparkle, + enableCrateResearcher, }); } @@ -151,12 +173,12 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { .section { margin-bottom: 24px; } - .agent-list, .component-list { + .agent-list { display: flex; flex-direction: column; gap: 8px; } - .agent-item, .component-item { + .agent-item { padding: 8px 12px; background: var(--vscode-list-inactiveSelectionBackground); border-radius: 4px; @@ -165,16 +187,13 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { justify-content: space-between; align-items: center; } - .agent-item:hover, .component-item:hover { + .agent-item:hover { background: var(--vscode-list-hoverBackground); } .agent-item.active { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); } - .component-item.disabled { - opacity: 0.6; - } .badges { display: flex; gap: 6px; @@ -199,6 +218,25 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { font-size: 11px; color: var(--vscode-descriptionForeground); } + .checkbox-item { + display: flex; + align-items: flex-start; + gap: 8px; + padding: 8px 0; + } + .checkbox-item input[type="checkbox"] { + margin-top: 2px; + cursor: pointer; + } + .checkbox-item label { + cursor: pointer; + flex: 1; + } + .checkbox-description { + font-size: 12px; + color: var(--vscode-descriptionForeground); + margin-top: 4px; + } @@ -211,8 +249,36 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider {

    Components

    -
    -
    Loading...
    +
    + + +
    +
    + + +
    +
    + +
    +

    Preferences

    +
    + +
    @@ -234,16 +300,40 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { vscode.postMessage({ type: 'open-settings' }); }; + // Handle require modifier to send checkbox + document.getElementById('require-modifier-to-send').onchange = (e) => { + vscode.postMessage({ type: 'toggle-require-modifier-to-send' }); + }; + + // Handle component checkboxes + document.getElementById('component-sparkle').onchange = (e) => { + vscode.postMessage({ type: 'toggle-component', componentId: 'sparkle' }); + }; + document.getElementById('component-crate-researcher').onchange = (e) => { + vscode.postMessage({ type: 'toggle-component', componentId: 'crate-researcher' }); + }; + // Handle messages from extension window.addEventListener('message', event => { const message = event.data; if (message.type === 'config') { renderAgents(message.agents, message.currentAgent); - renderComponents(message.components); + renderComponents(message.enableSparkle, message.enableCrateResearcher); + renderPreferences(message.requireModifierToSend); } }); + function renderComponents(enableSparkle, enableCrateResearcher) { + document.getElementById('component-sparkle').checked = enableSparkle; + document.getElementById('component-crate-researcher').checked = enableCrateResearcher; + } + + function renderPreferences(requireModifierToSend) { + const checkbox = document.getElementById('require-modifier-to-send'); + checkbox.checked = requireModifierToSend; + } + function renderAgents(agents, currentAgent) { const list = document.getElementById('agent-list'); list.innerHTML = ''; @@ -285,23 +375,7 @@ export class SettingsViewProvider implements vscode.WebviewViewProvider { } } - function renderComponents(components) { - const list = document.getElementById('component-list'); - list.innerHTML = ''; - for (const [name, config] of Object.entries(components)) { - const item = document.createElement('div'); - item.className = 'component-item' + (config.disabled ? ' disabled' : ''); - item.innerHTML = \` - \${name} - \${config.disabled ? 'Disabled' : 'Enabled'} - \`; - item.onclick = () => { - vscode.postMessage({ type: 'toggle-component', componentName: name }); - }; - list.appendChild(item); - } - } `; diff --git a/vscode-extension/src/symposium-webview.ts b/vscode-extension/src/symposium-webview.ts index d4e01fd3..50eab3ec 100644 --- a/vscode-extension/src/symposium-webview.ts +++ b/vscode-extension/src/symposium-webview.ts @@ -5,6 +5,7 @@ import { MynahUI, ChatItem, ChatItemType } from "@aws/mynah-ui"; declare const acquireVsCodeApi: any; declare const window: any & { SYMPOSIUM_EXTENSION_ACTIVATION_ID: string; + SYMPOSIUM_REQUIRE_MODIFIER_TO_SEND: boolean; }; // Import uuid - note: webpack will bundle this for browser @@ -355,6 +356,9 @@ const config: any = { noTabsOpen: "### Join the symposium by opening a tab", spinnerText: "Discussing with the Symposium...", }, + // When true, Enter adds newline and Shift/Cmd+Enter sends + // When false (default), Enter sends and Shift+Enter adds newline + requireModifierToSendPrompt: window.SYMPOSIUM_REQUIRE_MODIFIER_TO_SEND, }, defaults: { store: { @@ -536,7 +540,20 @@ vscode.postMessage({ type: "webview-ready" }); // Save state helper function saveState() { - // Get current tabs from mynah UI + // Sync current prompt input text to store for each tab before saving + // This captures any text the user is currently typing + const allTabs = mynahUI?.getAllTabs(); + if (allTabs) { + for (const tabId of Object.keys(allTabs)) { + const currentPromptText = mynahUI.getPromptInputText(tabId); + if (currentPromptText) { + // Update the store with the current prompt input text + mynahUI.updateStore(tabId, { promptInputText: currentPromptText }); + } + } + } + + // Get current tabs from mynah UI (now includes synced prompt input text) const currentTabs = mynahUI?.getAllTabs(); const state: WebviewState = { diff --git a/vscode-extension/src/test/cancellation.test.ts b/vscode-extension/src/test/cancellation.test.ts new file mode 100644 index 00000000..97116170 --- /dev/null +++ b/vscode-extension/src/test/cancellation.test.ts @@ -0,0 +1,132 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; +import { logger } from "../extension"; +import { LogEvent } from "../logger"; + +suite("Cancellation Tests", () => { + // Skip: This test is timing-dependent and unreliable with fast agents like ElizACP. + // See https://github.com/symposium-dev/symposium/issues/58 for proper fix. + test.skip("Should cancel previous prompt when sending a new one", async function () { + // This test needs time for agent spawning and response + this.timeout(30000); + + // Capture log events + const logEvents: LogEvent[] = []; + const logDisposable = logger.onLog((event) => { + logEvents.push(event); + }); + + // Activate the extension + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); + assert.ok(extension); + await extension.activate(); + + // Show the chat view + await vscode.commands.executeCommand("symposium.chatView.focus"); + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Create a tab + console.log("Creating test tab..."); + await vscode.commands.executeCommand( + "symposium.test.simulateNewTab", + "test-tab-cancellation", + ); + + // Wait for agent to spawn and session to be created + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // Verify tab exists + const tabs = (await vscode.commands.executeCommand( + "symposium.test.getTabs", + )) as string[]; + assert.ok(tabs.includes("test-tab-cancellation"), "Tab should exist"); + + // Start capturing agent responses + await vscode.commands.executeCommand( + "symposium.test.startCapturingResponses", + "test-tab-cancellation", + ); + + // Send first prompt - don't await the response + console.log("Sending first prompt..."); + const firstPromptPromise = vscode.commands.executeCommand( + "symposium.test.sendPrompt", + "test-tab-cancellation", + "Tell me a very long story about a dragon", + ); + + // Wait briefly to let the first prompt start processing + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify there's an active prompt + const hasActivePrompt = (await vscode.commands.executeCommand( + "symposium.test.hasActivePrompt", + "test-tab-cancellation", + )) as boolean; + console.log(`Has active prompt after first send: ${hasActivePrompt}`); + + // Clear log events to focus on cancellation + const preSecondPromptLogCount = logEvents.length; + + // Send second prompt - this should trigger cancellation of the first + console.log("Sending second prompt (should trigger cancellation)..."); + await vscode.commands.executeCommand( + "symposium.test.sendPrompt", + "test-tab-cancellation", + "Hello, how are you?", + ); + + // Wait for response + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Wait for first prompt to complete (it may have been cancelled) + await firstPromptPromise; + + // Get the response + const response = (await vscode.commands.executeCommand( + "symposium.test.getResponse", + "test-tab-cancellation", + )) as string; + + console.log(`Response received: ${response.slice(0, 100)}...`); + + // Stop capturing + await vscode.commands.executeCommand( + "symposium.test.stopCapturingResponses", + "test-tab-cancellation", + ); + + // Clean up + logDisposable.dispose(); + + // Check for cancellation log events + const cancellationEvents = logEvents.filter( + (e) => + e.category === "agent" && + e.message === "Cancelling previous prompt before new one", + ); + + const cancelSessionEvents = logEvents.filter( + (e) => e.category === "agent" && e.message === "Cancelling session", + ); + + console.log(`\nCancellation test summary:`); + console.log(`- Total log events: ${logEvents.length}`); + console.log(`- Cancellation trigger events: ${cancellationEvents.length}`); + console.log(`- Cancel session events: ${cancelSessionEvents.length}`); + console.log(`- Response length: ${response.length} characters`); + + // Verify that cancellation was triggered + assert.ok( + cancellationEvents.length >= 1, + "Should have triggered cancellation of previous prompt", + ); + assert.ok( + cancelSessionEvents.length >= 1, + "Should have sent cancel to agent session", + ); + + // Verify we still got a response (from the second prompt) + assert.ok(response.length > 0, "Should receive a response from the agent"); + }); +}); diff --git a/vscode-extension/src/test/conversation.test.ts b/vscode-extension/src/test/conversation.test.ts index efc8980d..8f64cea6 100644 --- a/vscode-extension/src/test/conversation.test.ts +++ b/vscode-extension/src/test/conversation.test.ts @@ -15,7 +15,7 @@ suite("Conversation Tests", () => { }); // Activate the extension - const extension = vscode.extensions.getExtension("symposium.symposium"); + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); assert.ok(extension); await extension.activate(); diff --git a/vscode-extension/src/test/extension.test.ts b/vscode-extension/src/test/extension.test.ts index aec901c8..20690646 100644 --- a/vscode-extension/src/test/extension.test.ts +++ b/vscode-extension/src/test/extension.test.ts @@ -3,12 +3,12 @@ import * as vscode from "vscode"; suite("Extension Test Suite", () => { test("Extension should be present", async () => { - const extension = vscode.extensions.getExtension("symposium.symposium"); + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); assert.ok(extension, "Extension should be installed"); }); test("Extension should activate", async () => { - const extension = vscode.extensions.getExtension("symposium.symposium"); + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); assert.ok(extension); await extension.activate(); @@ -16,7 +16,7 @@ suite("Extension Test Suite", () => { }); test("Chat view should be registered", async () => { - const extension = vscode.extensions.getExtension("symposium.symposium"); + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); assert.ok(extension); await extension.activate(); diff --git a/vscode-extension/src/test/lifecycle.test.ts b/vscode-extension/src/test/lifecycle.test.ts index f826f663..2ac900ed 100644 --- a/vscode-extension/src/test/lifecycle.test.ts +++ b/vscode-extension/src/test/lifecycle.test.ts @@ -15,7 +15,7 @@ suite("Webview Lifecycle Tests", () => { }); // Activate the extension - const extension = vscode.extensions.getExtension("symposium.symposium"); + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); assert.ok(extension); await extension.activate(); diff --git a/vscode-extension/src/test/multi-tab.test.ts b/vscode-extension/src/test/multi-tab.test.ts index 318d40a3..dfd4a3a7 100644 --- a/vscode-extension/src/test/multi-tab.test.ts +++ b/vscode-extension/src/test/multi-tab.test.ts @@ -15,7 +15,7 @@ suite("Multi-Tab Tests", () => { }); // Activate the extension - const extension = vscode.extensions.getExtension("symposium.symposium"); + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); assert.ok(extension); await extension.activate(); diff --git a/vscode-extension/src/test/settings.test.ts b/vscode-extension/src/test/settings.test.ts new file mode 100644 index 00000000..e9d83756 --- /dev/null +++ b/vscode-extension/src/test/settings.test.ts @@ -0,0 +1,273 @@ +import * as assert from "assert"; +import * as vscode from "vscode"; + +suite("Settings Test Suite", () => { + // Test that settings are properly registered in package.json + suite("Settings Registration", () => { + test("symposium.requireModifierToSend should be registered", async () => { + const config = vscode.workspace.getConfiguration("symposium"); + const inspect = config.inspect("requireModifierToSend"); + + assert.ok(inspect, "Setting should exist"); + assert.strictEqual( + inspect.defaultValue, + false, + "Default value should be false", + ); + }); + + test("symposium.enableSparkle should be registered", async () => { + const config = vscode.workspace.getConfiguration("symposium"); + const inspect = config.inspect("enableSparkle"); + + assert.ok(inspect, "Setting should exist"); + assert.strictEqual( + inspect.defaultValue, + true, + "Default value should be true", + ); + }); + + test("symposium.enableCrateResearcher should be registered", async () => { + const config = vscode.workspace.getConfiguration("symposium"); + const inspect = config.inspect("enableCrateResearcher"); + + assert.ok(inspect, "Setting should exist"); + assert.strictEqual( + inspect.defaultValue, + true, + "Default value should be true", + ); + }); + }); + + // Test that settings can be read and written + suite("Settings Read/Write", () => { + // Store original values to restore after tests + let originalRequireModifier: boolean | undefined; + let originalEnableSparkle: boolean | undefined; + let originalEnableCrateResearcher: boolean | undefined; + + suiteSetup(async () => { + const config = vscode.workspace.getConfiguration("symposium"); + originalRequireModifier = config.get("requireModifierToSend"); + originalEnableSparkle = config.get("enableSparkle"); + originalEnableCrateResearcher = config.get( + "enableCrateResearcher", + ); + }); + + suiteTeardown(async () => { + // Restore original values + const config = vscode.workspace.getConfiguration("symposium"); + if (originalRequireModifier !== undefined) { + await config.update( + "requireModifierToSend", + originalRequireModifier, + vscode.ConfigurationTarget.Global, + ); + } + if (originalEnableSparkle !== undefined) { + await config.update( + "enableSparkle", + originalEnableSparkle, + vscode.ConfigurationTarget.Global, + ); + } + if (originalEnableCrateResearcher !== undefined) { + await config.update( + "enableCrateResearcher", + originalEnableCrateResearcher, + vscode.ConfigurationTarget.Global, + ); + } + }); + + test("requireModifierToSend can be toggled", async () => { + // Get current value + const initialValue = + vscode.workspace + .getConfiguration("symposium") + .get("requireModifierToSend") ?? false; + + // Toggle it + await vscode.workspace + .getConfiguration("symposium") + .update( + "requireModifierToSend", + !initialValue, + vscode.ConfigurationTarget.Global, + ); + + // Must re-fetch config to see updated value + const newValue = vscode.workspace + .getConfiguration("symposium") + .get("requireModifierToSend"); + assert.strictEqual(newValue, !initialValue, "Value should be toggled"); + + // Toggle back + await vscode.workspace + .getConfiguration("symposium") + .update( + "requireModifierToSend", + initialValue, + vscode.ConfigurationTarget.Global, + ); + }); + + test("enableSparkle can be toggled", async () => { + const initialValue = + vscode.workspace + .getConfiguration("symposium") + .get("enableSparkle") ?? true; + + await vscode.workspace + .getConfiguration("symposium") + .update( + "enableSparkle", + !initialValue, + vscode.ConfigurationTarget.Global, + ); + + const newValue = vscode.workspace + .getConfiguration("symposium") + .get("enableSparkle"); + assert.strictEqual(newValue, !initialValue, "Value should be toggled"); + + await vscode.workspace + .getConfiguration("symposium") + .update( + "enableSparkle", + initialValue, + vscode.ConfigurationTarget.Global, + ); + }); + + test("enableCrateResearcher can be toggled", async () => { + const initialValue = + vscode.workspace + .getConfiguration("symposium") + .get("enableCrateResearcher") ?? true; + + await vscode.workspace + .getConfiguration("symposium") + .update( + "enableCrateResearcher", + !initialValue, + vscode.ConfigurationTarget.Global, + ); + + const newValue = vscode.workspace + .getConfiguration("symposium") + .get("enableCrateResearcher"); + assert.strictEqual(newValue, !initialValue, "Value should be toggled"); + + await vscode.workspace + .getConfiguration("symposium") + .update( + "enableCrateResearcher", + initialValue, + vscode.ConfigurationTarget.Global, + ); + }); + }); + + // Test that settings flow correctly to webview HTML generation + suite("Settings Flow to Webview", () => { + test("Chat view loads with settings without error", async function () { + this.timeout(10000); + + // Activate the extension + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); + assert.ok(extension); + await extension.activate(); + + // Set a known value for the setting + const originalValue = vscode.workspace + .getConfiguration("symposium") + .get("requireModifierToSend"); + + // Update setting to true + await vscode.workspace + .getConfiguration("symposium") + .update( + "requireModifierToSend", + true, + vscode.ConfigurationTarget.Global, + ); + + // Focus the chat view to trigger HTML generation with the setting + await vscode.commands.executeCommand("symposium.chatView.focus"); + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Verify setting persisted (re-fetch config) + const currentValue = vscode.workspace + .getConfiguration("symposium") + .get("requireModifierToSend"); + assert.strictEqual( + currentValue, + true, + "Setting should be true after update", + ); + + // Restore original value + await vscode.workspace + .getConfiguration("symposium") + .update( + "requireModifierToSend", + originalValue ?? false, + vscode.ConfigurationTarget.Global, + ); + }); + + test("Settings view loads and responds to configuration changes", async function () { + this.timeout(10000); + + // Activate the extension + const extension = vscode.extensions.getExtension("symposium-dev.symposium"); + assert.ok(extension); + await extension.activate(); + + // Focus the settings view + await vscode.commands.executeCommand("symposium.settingsView.focus"); + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Update a setting - the SettingsViewProvider listens for changes + // and sends updated config to the webview + const originalSparkle = + vscode.workspace + .getConfiguration("symposium") + .get("enableSparkle") ?? true; + + await vscode.workspace + .getConfiguration("symposium") + .update( + "enableSparkle", + !originalSparkle, + vscode.ConfigurationTarget.Global, + ); + + // Give time for configuration change event to fire + await new Promise((resolve) => setTimeout(resolve, 200)); + + // Verify setting changed (re-fetch config) + const newValue = vscode.workspace + .getConfiguration("symposium") + .get("enableSparkle"); + assert.strictEqual( + newValue, + !originalSparkle, + "Setting should be toggled", + ); + + // Restore + await vscode.workspace + .getConfiguration("symposium") + .update( + "enableSparkle", + originalSparkle, + vscode.ConfigurationTarget.Global, + ); + }); + }); +}); diff --git a/zed-extension/LICENSE b/zed-extension/LICENSE new file mode 100644 index 00000000..857ec8a4 --- /dev/null +++ b/zed-extension/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Symposium Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/zed-extension/README.md b/zed-extension/README.md new file mode 100644 index 00000000..73107c05 --- /dev/null +++ b/zed-extension/README.md @@ -0,0 +1,33 @@ +# Symposium Zed Extension + +This extension adds Symposium as an agent server in Zed. + +## What is Symposium? + +Symposium wraps Claude Code with additional components: + +- **Sparkle**: Collaborative AI identity and memory +- **Rust Crate Researcher**: Rust crate documentation lookup + +## Installation + +Install from the Zed extension marketplace, or manually: + +1. Open Zed +2. Open the command palette (Cmd+Shift+P) +3. Search for "Extensions: Install Extension" +4. Search for "Symposium" + +## Usage + +After installation, Symposium will appear as an available agent in Zed's agent panel. + +## Requirements + +- Node.js (for npx to run Claude Code) +- Anthropic API key configured for Claude Code + +## Links + +- [Symposium Documentation](https://symposium-dev.github.io/symposium/) +- [GitHub Repository](https://github.com/symposium-dev/symposium) diff --git a/zed-extension/extension.toml b/zed-extension/extension.toml new file mode 100644 index 00000000..a9a11b28 --- /dev/null +++ b/zed-extension/extension.toml @@ -0,0 +1,36 @@ +id = "symposium" +name = "Symposium" +version = "0.1.0" +schema_version = 1 +authors = ["Niko Matsakis "] +description = "Collaborative AI with Sparkle identity and Rust crate research" +repository = "https://github.com/symposium-dev/symposium" + +[agent_servers.symposium] +name = "Symposium" +icon = "./icons/symposium.svg" + +[agent_servers.symposium.targets.darwin-aarch64] +archive = "https://github.com/symposium-dev/symposium/releases/latest/download/symposium-darwin-arm64.tar.gz" +cmd = "./symposium-acp-agent" +args = ["--", "npx", "-y", "@anthropic-ai/claude-code@latest"] + +[agent_servers.symposium.targets.darwin-x86_64] +archive = "https://github.com/symposium-dev/symposium/releases/latest/download/symposium-darwin-x64.tar.gz" +cmd = "./symposium-acp-agent" +args = ["--", "npx", "-y", "@anthropic-ai/claude-code@latest"] + +[agent_servers.symposium.targets.linux-x86_64] +archive = "https://github.com/symposium-dev/symposium/releases/latest/download/symposium-linux-x64.tar.gz" +cmd = "./symposium-acp-agent" +args = ["--", "npx", "-y", "@anthropic-ai/claude-code@latest"] + +[agent_servers.symposium.targets.linux-aarch64] +archive = "https://github.com/symposium-dev/symposium/releases/latest/download/symposium-linux-arm64.tar.gz" +cmd = "./symposium-acp-agent" +args = ["--", "npx", "-y", "@anthropic-ai/claude-code@latest"] + +[agent_servers.symposium.targets.windows-x86_64] +archive = "https://github.com/symposium-dev/symposium/releases/latest/download/symposium-windows-x64.zip" +cmd = "./symposium-acp-agent.exe" +args = ["--", "npx", "-y", "@anthropic-ai/claude-code@latest"] diff --git a/zed-extension/icons/symposium.svg b/zed-extension/icons/symposium.svg new file mode 100644 index 00000000..15862117 --- /dev/null +++ b/zed-extension/icons/symposium.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file