diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..1f83b7d881 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Global ownership +# Bryan Ollendyke (btopro) is the primary maintainer and creator of HAXTheWeb +* @btopro + +# HAXTheWeb ecosystem is developed and maintained by: +# Bryan Ollendyke (@btopro) - Penn State University +# Copyright (c) 2015-2025 The Pennsylvania State University \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..451f1a9b13 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,72 @@ +## Related Issue + +Closes [ISSUE #XXXX](https://github.com/haxtheweb/issues/issues/XXXX) + +## Figma Link + + +## Description of Changes + + +### What changed: +- +- +- + +### Why this change was needed: + + +## Type of Change + +- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) +- [ ] ✨ New feature (non-breaking change which adds functionality) +- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] 📚 Documentation update +- [ ] 🎨 Style/formatting changes +- [ ] ♻️ Code refactoring +- [ ] 🔧 Configuration changes + +## Testing Checklist + +- [ ] I have tested this change locally +- [ ] I have added/updated tests for my changes +- [ ] All existing tests pass +- [ ] I have tested on multiple browsers (if applicable) +- [ ] I have tested on mobile devices (if applicable) +- [ ] I have verified accessibility compliance +- [ ] I have tested with screen readers (if applicable) + +## Quality Assurance + +- [ ] I have followed the project's coding conventions +- [ ] I have updated documentation where necessary +- [ ] I have added comments to complex code +- [ ] My changes don't introduce console warnings/errors +- [ ] I have checked for performance implications + +## Ways to Test This Change + +1. +2. +3. + +## Screenshots/Recordings + + +### Before: + + +### After: + + +## Additional Notes + + +## Checklist + +- [ ] I have read the [contributing guidelines](../CONTRIBUTING.md) +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] Any dependent changes have been merged and published \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 993c2ee7bc..8ff1f2e56c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,10 +11,10 @@ jobs: fail-fast: false matrix: platform: [ubuntu-latest] - node-version: [20] + node-version: [22] include: - platform: ubuntu-latest - node-version: 20 + node-version: 22 runs-on: ${{ matrix.platform }} steps: - name: Checkout diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 0000000000..7dbaf7afa5 --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,37 @@ +name: "CLA Assistant" +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, closed, synchronize] + +# explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings +permissions: + actions: write + contents: write + pull-requests: write + statuses: write + +jobs: + CLAAssistant: + runs-on: ubuntu-latest + steps: + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@v2.6.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + path-to-signatures: 'signatures/version1/cla.json' + path-to-document: 'https://github.com/haxtheweb/webcomponents/blob/main/CLA.md' + # branch should not be protected + branch: 'main' + allowlist: 'btopro,*[bot]' + # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken + remote-organization-name: '' + remote-repository-name: '' + create-file-commit-message: 'Creating file for storing CLA Signatures' + signed-commit-message: 'CLA signed by $contributorName' + custom-notsigned-prcomment: 'Thank you for your contribution! Before we can merge your PR, we need you to sign our Contributor License Agreement (CLA). Please comment **"I have read the CLA Document and I hereby sign the CLA"** to agree to the [CLA terms](https://github.com/haxtheweb/webcomponents/blob/main/CLA.md).' + custom-pr-sign-comment: 'Thank you for signing the CLA! Your contribution will now be reviewed.' + custom-allsigned-prcomment: 'All contributors have signed the CLA. Thank you!' \ No newline at end of file diff --git a/.github/workflows/ossf_scorecard.yml b/.github/workflows/ossf_scorecard.yml new file mode 100644 index 0000000000..bc6a53d987 --- /dev/null +++ b/.github/workflows/ossf_scorecard.yml @@ -0,0 +1,63 @@ +--- +# This workflow uses actions that are not certified by GitHub. They are provided by a third-party and are governed by separate terms of service, privacy policy, and support documentation. +name: OSSF Scorecard +on: + # For Branch-Protection check. Only the default branch is supported. See https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection. + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained. + schedule: + - cron: "0 0 * * 1" + push: + branches: [main, master] + workflow_dispatch: +# Declare default permissions as read only. +permissions: read-all +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-24.04 + # Delete the conditional below if you are using the OSSF Scorecard on a public repository. + if: ${{ github.event.repository.private == false }} + permissions: + # Needed if using Code Scanning alerts. + security-events: write + # Needed for GitHub OIDC token if publish_results is true. + id-token: write + # Uncomment the permissions below if you are using the OSSF Scorecard on a private repository. + # contents: read + # actions: read + # issues: read # To allow GraphQL ListCommits to work + # pull-requests: read # To allow GraphQL ListCommits to work + # checks: read # To detect SAST tools + steps: + - name: Check out the codebase + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Run analysis + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) fine-grained personal access token. Uncomment the `repo_token` line below if you want to enable the Branch-Protection or Webhooks check on a *private* repository. + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Publish the results for public repositories to enable scorecard badges. For more details, see https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories, `publish_results` will automatically be set to `false`, regardless of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF format to the repository Actions tab. + - name: Upload artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: Upload SARIF results to code scanning + uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + with: + sarif_file: results.sarif \ No newline at end of file diff --git a/.gitignore b/.gitignore index 735c5df661..fb74bf6380 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ yarn-error.log yarn.lock test/index.html component-gallery.html +elements/video-player/demo/samples/ad/ elements/haxcms-elements/demo/files/ elements/grade-book/demo/psu/ elements/elmsln-apps/lib/lrnapp-studio-instructor/demo/* diff --git a/.wcflibcache.json b/.wcflibcache.json index cda3090190..db4df544ea 100644 --- a/.wcflibcache.json +++ b/.wcflibcache.json @@ -20,9 +20,6 @@ "devDependencies": { "web-animations-js": "2.3.2", "@haxtheweb/deduping-fix": "^9.0.0-alpha.0", - "@polymer/iron-demo-helpers": "3.1.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@webcomponents/webcomponentsjs": "^2.8.0", "gulp-babel": "8.0.0", "@web/dev-server": "0.4.6", "concurrently": "5.3.0", @@ -54,13 +51,10 @@ "lighthouse": "gulp lighthouse --gulpfile=gulpfile.cjs" }, "dependencies": { - "lit": "^3.3.0" + "lit": "^3.3.1" }, "devDependencies": { "@haxtheweb/deduping-fix": "^9.0.0-alpha.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@polymer/iron-demo-helpers": "3.1.0", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "5.3.0", "gulp-babel": "8.0.0", "polymer-build": "3.1.4", diff --git a/CLA-README.md b/CLA-README.md new file mode 100644 index 0000000000..e913b11f8f --- /dev/null +++ b/CLA-README.md @@ -0,0 +1,47 @@ +# Contributor License Agreement (CLA) Process + +## How it Works + +This repository uses an automated CLA (Contributor License Agreement) process to ensure legal compliance for all contributions. Here's how it works: + +### For New Contributors + +1. **Open a Pull Request**: When you submit your first pull request, the CLA bot will automatically post a comment asking you to sign the CLA. + +2. **Sign the CLA**: To sign the CLA, simply comment on your pull request with: + ``` + I have read the CLA Document and I hereby sign the CLA + ``` + +3. **Automatic Processing**: Once you sign, the bot will: + - Record your signature in the repository + - Update the pull request status + - Allow your PR to proceed with the review process + +### For Returning Contributors + +If you've already signed the CLA for this repository, you don't need to sign it again for future pull requests. The bot will automatically recognize you as a signed contributor. + +### Important Notes + +- **Exact Comment Required**: You must use the exact comment text: `I have read the CLA Document and I hereby sign the CLA` +- **Read the CLA**: Please read the [full CLA document](./CLA.md) before signing +- **One-Time Process**: You only need to sign once per repository +- **Bot Users**: Automated bot users are automatically allowlisted and don't need to sign + +### Troubleshooting + +If you're having issues with the CLA process: + +1. **Re-check Status**: Comment `recheck` on your pull request to have the bot re-evaluate your CLA status +2. **Contact Maintainers**: If problems persist, mention a maintainer in your pull request + +### Technical Details + +- CLA signatures are stored in `signatures/version1/cla.json` in this repository +- The CLA bot is implemented using the [contributor-assistant/github-action](https://github.com/contributor-assistant/github-action) +- No third-party services are used; everything is handled within GitHub + +--- + +**Questions?** Feel free to open an issue or ask in your pull request if you need help with the CLA process. \ No newline at end of file diff --git a/CLA.md b/CLA.md new file mode 100644 index 0000000000..99e652421d --- /dev/null +++ b/CLA.md @@ -0,0 +1,40 @@ +# Contributor License Agreement + +## HAXtheWeb Webcomponents Project + +Thank you for your interest in contributing to the HAXtheWeb Webcomponents project owned by Penn State University ("We" or "Us"). + +This Contributor License Agreement ("Agreement") documents the rights granted by contributors to Us. To make this document effective, please sign it and send it to Us. + +### 1. Definitions + +**"You"** (or **"Your"**) shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Us. + +**"Contribution"** shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Us for inclusion in, or documentation of, any of the products owned or managed by Us (the "Work"). + +### 2. Grant of Rights + +#### Copyright License +Subject to the terms and conditions of this Agreement, You hereby grant to Us and to recipients of software distributed by Us a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Work, and to permit persons to whom the Work 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 Work. + +#### Patent License +Subject to the terms and conditions of this Agreement, You hereby grant to Us and to recipients of software distributed by Us 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. + +### 3. Representations + +You represent that: + +1. You are legally entitled to grant the above license. +2. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you have received permission to make Contributions on behalf of that employer, or your employer has waived such rights for your Contributions to Us. +3. Each of Your Contributions is Your original creation. +4. Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. + +### 4. You are not expected to provide support + +You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your 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. + +--- + +**By commenting "I have read the CLA Document and I hereby sign the CLA" on a Pull Request, you are agreeing to the terms of this Contributor License Agreement.** \ No newline at end of file diff --git a/README.md b/README.md index 9032c4d1c0..afabd4132f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ +[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/haxtheweb/webcomponents/badge)](https://securityscorecards.dev/viewer/?uri=github.com/haxtheweb/webcomponents) +[![Community Support](https://badgen.net/badge/support/community/cyan?icon=awesome)](/SUPPORT.md) [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](/CODE_OF_CONDUCT.md) +[![Discord](https://badgen.net/badge/icon/discord?icon=discord&label)](https://discord.gg/EKYJAjqGhf) [![lerna](https://img.shields.io/badge/maintained%20with-lerna-cc00ff.svg)](https://lerna.js.org/) [![Lit](https://img.shields.io/badge/-Lit-324fff?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyBmaWxsPSIjZmZmIiB2aWV3Qm94PSIwIDAgMTYwIDIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMTYwIDgwdjgwbC00MC00MHptLTQwIDQwdjgwbDQwLTQwem0wLTgwdjgwbC00MC00MHptLTQwIDQwdjgwbDQwLTQwem0tNDAtNDB2ODBsNDAtNDB6bTQwLTQwdjgwbC00MC00MHptLTQwIDEyMHY4MGwtNDAtNDB6bS00MC00MHY4MGw0MC00MHoiLz48L3N2Zz4%3D)](https://lit.dev/) [![#HAXTheWeb](https://img.shields.io/badge/-HAXTheWeb-999999FF?style=flat&logo=data:image/svg%2bxml;base64,PHN2ZyBpZD0iZmVhMTExZTAtMjEwZC00Y2QwLWJhMWQtZGZmOTQyODc0Njg1IiBkYXRhLW5hbWU9IkxheWVyIDEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDE4NC40IDEzNS45NyI+PGRlZnM+PHN0eWxlPi5lMWJjMjAyNS0xODAwLTRkYzItODc4NS1jNDZlZDEwM2Y0OTJ7ZmlsbDojMjMxZjIwO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iZTFiYzIwMjUtMTgwMC00ZGMyLTg3ODUtYzQ2ZWQxMDNmNDkyIiBkPSJNNzguMDcsODMuNDVWNTVIODYuMnY4LjEzaDE2LjI2djQuMDdoNC4wN1Y4My40NUg5OC40VjY3LjE5SDg2LjJWODMuNDVaIi8+PHBvbHlnb24gcG9pbnRzPSIxNTMuMTMgNjMuNyAxNTMuMTMgNTEuMzkgMTQwLjU0IDUxLjM5IDE0MC41NCAzOS4wOSAxMjcuOTUgMzkuMDkgMTI3Ljk1IDI2Ljc5IDEwMi43OCAyNi43OSAxMDIuNzggMzkuMDkgMTE1LjM2IDM5LjA5IDExNS4zNiA1MS4zOSAxMjcuOTUgNTEuMzkgMTI3Ljk1IDYzLjcgMTQwLjU0IDYzLjcgMTQwLjU0IDc2IDEyNy4zNiA3NiAxMjcuMzYgODguMyAxMTQuNzggODguMyAxMTQuNzggMTAwLjYxIDEwMi4xOSAxMDAuNjEgMTAyLjE5IDExMi45MSAxMjcuMzYgMTEyLjkxIDEyNy4zNiAxMDAuNjEgMTM5Ljk1IDEwMC42MSAxMzkuOTUgODguMyAxNTIuNTQgODguMyAxNTIuNTQgNzYgMTY1LjcyIDc2IDE2NS43MiA2My43IDE1My4xMyA2My43Ii8+PHBvbHlnb24gcG9pbnRzPSIzMy4xMyA2My43IDMzLjEzIDUxLjM5IDQ1LjcyIDUxLjM5IDQ1LjcyIDM5LjA5IDU4LjMxIDM5LjA5IDU4LjMxIDI2Ljc5IDgzLjQ4IDI2Ljc5IDgzLjQ4IDM5LjA5IDcwLjg5IDM5LjA5IDcwLjg5IDUxLjM5IDU4LjMxIDUxLjM5IDU4LjMxIDYzLjcgNDUuNzIgNjMuNyA0NS43MiA3NiA1OC44OSA3NiA1OC44OSA4OC4zIDcxLjQ4IDg4LjMgNzEuNDggMTAwLjYxIDg0LjA3IDEwMC42MSA4NC4wNyAxMTIuOTEgNTguODkgMTEyLjkxIDU4Ljg5IDEwMC42MSA0Ni4zMSAxMDAuNjEgNDYuMzEgODguMyAzMy43MiA4OC4zIDMzLjcyIDc2IDIwLjU0IDc2IDIwLjU0IDYzLjcgMzMuMTMgNjMuNyIvPjwvc3ZnPg==)](https://haxtheweb.org/) diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000000..579b856956 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,31 @@ +# Support + +## Getting Help + +If you need help with this project, please use the following resources: + +### Community Support + +- **Discord**: Join our community Discord server for real-time help and discussion: [HAXTheWeb Discord](https://discord.gg/EKYJAjqGhf) +- **Documentation**: Visit our comprehensive documentation: [HAXTheWeb Docs](https://haxtheweb.org/documentation) +- **Issues**: For bug reports and feature requests, please use our unified issue queue: [HAXTheWeb Issues](https://github.com/haxtheweb/issues/issues) + +### Getting Started + +- Check out our [documentation](https://haxtheweb.org/documentation) for guides and tutorials +- Explore and play with HAX components: [HAX Magic Script Playground](https://hax.cloud/magicscript.html) +- Join the discussion on [Discord](https://discord.gg/EKYJAjqGhf) to connect with other developers + +### Before Opening an Issue + +Before creating a new issue, please: + +1. Search existing issues in our [unified issue queue](https://github.com/haxtheweb/issues/issues) +2. Check our [documentation](https://haxtheweb.org/documentation) +3. Ask for help on [Discord](https://discord.gg/EKYJAjqGhf) + +This helps keep our issue queue focused on actual bugs and feature requests. + +## Contributing + +We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details on how to get involved. \ No newline at end of file diff --git a/api/package.json b/api/package.json index 2f31b1cad0..70e34aace9 100644 --- a/api/package.json +++ b/api/package.json @@ -6,9 +6,9 @@ }, "dependencies": { "patch-package": "^8.0.0", - "lit-html": "3.3.0", + "lit-html": "3.3.1", "@lit-labs/ssr": "^3.3.1", - "lit": "3.3.0", + "lit": "3.3.1", "node-html-parser": "^6.1.13", "@haxtheweb/course-design": "^11.0.5", "@haxtheweb/lrn-math": "^11.0.5", diff --git a/automate-theme-screenshots.js b/automate-theme-screenshots.js new file mode 100644 index 0000000000..d14beb534c --- /dev/null +++ b/automate-theme-screenshots.js @@ -0,0 +1,142 @@ +#!/usr/bin/env node + +/** + * Complete HAX Theme Screenshot Automation Workflow + * + * This script provides the complete automation workflow for generating + * screenshots of all HAX themes and updating the registry. + * + * Usage: This is the automation guide for MCP puppeteer tools + */ + +import { readFileSync, writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const THEMES_JSON_PATH = join(__dirname, 'elements/haxcms-elements/lib/themes.json'); +const SCREENSHOTS_DIR = join(dirname(THEMES_JSON_PATH), 'theme-screenshots'); + +// Load themes +const themes = JSON.parse(readFileSync(THEMES_JSON_PATH, 'utf8')); +const themeList = Object.entries(themes); + +console.log('\n=== HAX Theme Screenshot Automation Workflow ===\n'); + +/** + * STEP 1: Verify Setup + */ +console.log('✓ Themes loaded:', themeList.length); +console.log('✓ Screenshots directory:', SCREENSHOTS_DIR); +console.log('✓ Updated themes.json with paths'); + +/** + * STEP 2: Automation Commands + * These are the MCP puppeteer commands to run: + */ + +console.log('\n=== PUPPETEER AUTOMATION COMMANDS ===\n'); + +console.log('1. Navigate to demo site:'); +console.log(` puppeteer_navigate({"allowDangerous":true, "launchOptions":{"args":["--no-sandbox", "--disable-setuid-sandbox"], "headless":true}, "url":"http://localhost:8080"})`); + +console.log('\n2. Wait for HAXCMS to load (run this first):'); +console.log(` puppeteer_evaluate({"script":"new Promise(resolve => { const wait = () => { if (globalThis.HAXCMS && globalThis.HAXCMS.setTheme) { console.log('HAXCMS ready!'); const currentTheme = globalThis.HAXCMS.instance && globalThis.HAXCMS.instance.store && globalThis.HAXCMS.instance.store.manifest && globalThis.HAXCMS.instance.store.manifest.metadata && globalThis.HAXCMS.instance.store.manifest.metadata.theme && globalThis.HAXCMS.instance.store.manifest.metadata.theme.element; resolve({ready: true, currentTheme: currentTheme}); } else { console.log('Still waiting...'); setTimeout(wait, 2000); } }; wait(); })"})`); + +console.log('\n3. Theme switching and screenshot commands:'); +console.log(' For each theme, run these 3 commands in sequence:\n'); + +// Generate commands for first 5 themes as examples +const exampleThemes = themeList.slice(0, 5); +exampleThemes.forEach(([themeKey, themeData], index) => { + console.log(`--- Theme ${index + 1}: ${themeData.name} (${themeData.element}) ---`); + console.log(`a) Switch theme:`); + console.log(` puppeteer_evaluate({"script":"globalThis.HAXCMS.setTheme('${themeData.element}'); 'Theme switched to ${themeData.element}'"})`); + console.log(`b) Wait for theme to load (3 seconds):`); + console.log(` puppeteer_evaluate({"script":"new Promise(resolve => setTimeout(() => resolve('Theme loaded'), 3000))"})`); + console.log(`c) Take screenshot:`); + console.log(` puppeteer_screenshot({"height":800, "width":1280, "name":"${themeData.element}"})`); + console.log(''); +}); + +console.log(`... repeat for all ${themeList.length} themes\n`); + +/** + * STEP 3: Popular themes for quick demo + */ +const popularThemes = [ + 'polaris-flex-sidebar', + 'polaris-flex-theme', + 'clean-one', + 'journey-theme', + 'learn-two-theme', + 'spacebook-theme', + 'bootstrap-theme' +].filter(themeName => themes[themeName]); + +console.log('=== QUICK DEMO: Popular Themes ===\n'); +console.log(`Popular themes to screenshot first (${popularThemes.length} themes):\n`); + +popularThemes.forEach((themeKey, index) => { + const themeData = themes[themeKey]; + console.log(`${index + 1}. ${themeData.name}`); + console.log(` Element: ${themeData.element}`); + console.log(` Switch: globalThis.HAXCMS.setTheme('${themeData.element}')`); + console.log(` Screenshot: ${themeData.element}.jpg`); + console.log(''); +}); + +/** + * STEP 4: File Saving + */ +console.log('\n=== FILE SAVING INSTRUCTIONS ===\n'); +console.log('When saving screenshot files:'); +console.log('1. Save each screenshot as: {theme-element-name}.jpg'); +console.log(`2. Save to directory: ${SCREENSHOTS_DIR}`); +console.log('3. The themes.json is already updated with correct paths'); +console.log('4. Screenshots will be relative to themes.json location'); + +console.log('\n=== VERIFICATION ===\n'); +console.log('After screenshots are taken, verify:'); +console.log('1. Check screenshot files exist in theme-screenshots/'); +console.log('2. Themes.json has correct paths'); +console.log('3. V2 app-hax can load themes.json and display screenshots'); + +/** + * Export automation data for programmatic use + */ +export const automationWorkflow = { + totalThemes: themeList.length, + popularThemes: popularThemes, + screenshotsDir: SCREENSHOTS_DIR, + + // Generate command for specific theme + generateCommands: (themeElement) => { + const theme = Object.values(themes).find(t => t.element === themeElement); + if (!theme) return null; + + return { + switchTheme: `globalThis.HAXCMS.setTheme('${theme.element}')`, + wait: `new Promise(resolve => setTimeout(() => resolve('Theme loaded'), 3000))`, + screenshot: { + name: theme.element, + width: 1280, + height: 800 + } + }; + }, + + // All themes list + allThemes: themeList.map(([key, data]) => ({ + key, + element: data.element, + name: data.name, + screenshotPath: `theme-screenshots/${data.element}.jpg` + })) +}; + +console.log('\n=== READY FOR AUTOMATION ==='); +console.log('Run the puppeteer commands above to generate all theme screenshots!'); +console.log('The complete theme registry will be ready for v2 app-hax use-cases.'); \ No newline at end of file diff --git a/component-gallery.html b/component-gallery.html index 96bf45a840..31c8e6739c 100644 --- a/component-gallery.html +++ b/component-gallery.html @@ -370,12 +370,12 @@

Web Components Gallery

-
200 Components Available
+
198 Components Available
-
- + - + \ No newline at end of file diff --git a/elements/app-hax/demo/sites.json b/elements/app-hax/demo/sites.json index b504e06024..0173990032 100644 --- a/elements/app-hax/demo/sites.json +++ b/elements/app-hax/demo/sites.json @@ -25,6 +25,7 @@ "name": "ist402", "created": 1565898366, "updated": 1615262120, + "category": ["Course"], "git": { "autoPush": false, "branch": "master", @@ -81,6 +82,7 @@ "name": "acctg211", "created": 1610048864, "updated": 1612555993, + "category": ["Blog"], "version": "2.0.9", "domain": "", "logo": "", @@ -149,6 +151,7 @@ "name": "geodz511", "created": 1569349769, "updated": 1640014827, + "category": ["Course"], "git": { "vendor": "github", "branch": "master", @@ -206,6 +209,7 @@ "logo": "assets/banner.jpg", "created": 1635276018, "updated": 1635276048, + "category": ["Brochure"], "git": {} }, "theme": { @@ -246,6 +250,7 @@ "name": "ist210", "created": 1592403069, "updated": 1598281185, + "category": ["Course"], "git": { "autoPush": false, "branch": "", @@ -296,6 +301,7 @@ "author": "", "description": "", "license": "by-sa", + "category": ["Blog"], "metadata": { "author": { "image": "", @@ -308,6 +314,7 @@ "logo": "assets/banner.jpg", "created": 1594994550, "updated": 1595266163, + "category": ["Course"], "git": { "autoPush": false, "branch": "", @@ -369,6 +376,7 @@ "logo": "assets/banner.jpg", "created": 1629208877, "updated": 1642013655, + "category": ["Course"], "git": { "autoPush": false, "branch": "", @@ -425,6 +433,7 @@ "logo": "assets/banner.jpg", "created": 1644856484, "updated": 1644856484, + "category": ["Course"], "git": {} }, "theme": { @@ -459,7 +468,8 @@ "domain": null, "site": { "created": 1558624482, - "updated": 1558624504 + "updated": 1558624504, + "category": ["Course"] }, "theme": { "element": "learn-two-theme", diff --git a/elements/app-hax/demo/skeletons.json b/elements/app-hax/demo/skeletons.json new file mode 100644 index 0000000000..d97c2d4b80 --- /dev/null +++ b/elements/app-hax/demo/skeletons.json @@ -0,0 +1,47 @@ +{ + "status": 200, + "data": [ + { + "title": "Course Site Template", + "description": "A structured template for online courses with lessons and modules", + "image": "/elements/haxcms-elements/lib/theme-screenshots/theme-clean-one-thumb.jpg", + "category": ["course", "education"], + "attributes": [ + { "icon": "hax:lesson", "tooltip": "Educational Content" } + ], + "demo-url": "https://oer.hax.psu.edu/bto108/sites/edtechjoker-course-skelton", + "skeleton-url": "#" + }, + { + "title": "Portfolio Site Template", + "description": "A clean template for showcasing work and projects", + "image": "/elements/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme-thumb.jpg", + "category": ["portfolio", "showcase"], + "attributes": [ + { "icon": "icons:perm-identity", "tooltip": "Personal Portfolio" } + ], + "demo-url": "#", + "skeleton-url": "#" + }, + { + "title": "Blog Site Template", + "description": "A template for creating blog sites with posts and categories", + "image": "/elements/haxcms-elements/lib/theme-screenshots/theme-haxma-theme-thumb.jpg", + "category": ["blog", "writing"], + "attributes": [{ "icon": "lrn:write", "tooltip": "Blog Content" }], + "demo-url": "#", + "skeleton-url": "#" + }, + { + "title": "Documentation Site", + "description": "Template for creating documentation and knowledge bases", + "image": "/elements/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme-thumb.jpg", + "category": ["documentation", "reference"], + "attributes": [ + { "icon": "icons:description", "tooltip": "Documentation" } + ], + "demo-url": "#", + "skeleton-url": "#" + } + ] +} diff --git a/elements/app-hax/index.html b/elements/app-hax/index.html index 3eb8fe524e..4516adf7b5 100644 --- a/elements/app-hax/index.html +++ b/elements/app-hax/index.html @@ -4,10 +4,10 @@ app-hax documentation - - + + - + diff --git a/elements/app-hax/lib/app-hax-theme.js b/elements/app-hax/lib/app-hax-theme.js index ad7687f203..f9ac6b8b1d 100644 --- a/elements/app-hax/lib/app-hax-theme.js +++ b/elements/app-hax/lib/app-hax-theme.js @@ -27,6 +27,8 @@ import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Course + * @haxcms-theme-internal false * @demo demo/index.html * @element app-hax-theme */ @@ -589,7 +591,6 @@ class AppHaxTheme extends HAXCMSRememberRoute( body { margin: 0; padding: 0; - font-family: "Press Start 2P", sans-serif; overflow-x: hidden; background-image: url("${unsafeCSS(LMGridBox)}"); background-repeat: repeat; @@ -646,7 +647,6 @@ class AppHaxTheme extends HAXCMSRememberRoute( } #loading { - font-family: "Press Start 2P", sans-serif; text-align: center; margin-top: 100px; } @@ -760,7 +760,6 @@ class AppHaxTheme extends HAXCMSRememberRoute( background-color: green; border: 4px solid black; border-radius: 8px; - font-family: "Press Start 2P", sans-serif; } simple-modal button.hax-modal-btn.cancel { background-color: red; diff --git a/elements/app-hax/lib/assets/images/DMBackgroundImage-min.jpg b/elements/app-hax/lib/assets/images/DMBackgroundImage-min.jpg new file mode 100644 index 0000000000..e92cda6291 Binary files /dev/null and b/elements/app-hax/lib/assets/images/DMBackgroundImage-min.jpg differ diff --git a/elements/app-hax/lib/assets/images/DMBackgroundImage.svg b/elements/app-hax/lib/assets/images/DMBackgroundImage.svg new file mode 100644 index 0000000000..ad5739bebc --- /dev/null +++ b/elements/app-hax/lib/assets/images/DMBackgroundImage.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elements/app-hax/lib/assets/images/LMBackgroundImage-min.jpg b/elements/app-hax/lib/assets/images/LMBackgroundImage-min.jpg new file mode 100644 index 0000000000..f9f51f23f2 Binary files /dev/null and b/elements/app-hax/lib/assets/images/LMBackgroundImage-min.jpg differ diff --git a/elements/app-hax/lib/assets/images/LMBackgroundImage.svg b/elements/app-hax/lib/assets/images/LMBackgroundImage.svg new file mode 100644 index 0000000000..e160fd480b --- /dev/null +++ b/elements/app-hax/lib/assets/images/LMBackgroundImage.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/elements/app-hax/lib/assets/images/scroll-button-DM.png b/elements/app-hax/lib/assets/images/scroll-button-DM.png new file mode 100644 index 0000000000..a5eecd5226 Binary files /dev/null and b/elements/app-hax/lib/assets/images/scroll-button-DM.png differ diff --git a/elements/app-hax/lib/assets/images/scroll-button-LM.png b/elements/app-hax/lib/assets/images/scroll-button-LM.png new file mode 100644 index 0000000000..6dd1fbf3d6 Binary files /dev/null and b/elements/app-hax/lib/assets/images/scroll-button-LM.png differ diff --git a/elements/app-hax/lib/v1/app-hax-site-details.js b/elements/app-hax/lib/v1/app-hax-site-details.js index b5d57549e2..945b3d0529 100644 --- a/elements/app-hax/lib/v1/app-hax-site-details.js +++ b/elements/app-hax/lib/v1/app-hax-site-details.js @@ -254,7 +254,7 @@ export class AppHaxSiteDetails extends SimpleColors { invokedBy: target, styles: { "--simple-modal-titlebar-background": "orange", - "--simple-modal-titlebar-color": "black", + "--simple-modal-titlebar-color": "light-dark(black, white)", "--simple-modal-width": "30vw", "--simple-modal-min-width": "300px", "--simple-modal-z-index": "100000000", @@ -273,7 +273,12 @@ export class AppHaxSiteDetails extends SimpleColors { cancelOperation() { store.activeSiteOp = ""; store.activeSiteId = null; - globalThis.dispatchEvent(new CustomEvent("simple-modal-hide")); + globalThis.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + }), + ); store.appEl.playSound("error"); } @@ -304,7 +309,12 @@ export class AppHaxSiteDetails extends SimpleColors { } }, ); - globalThis.dispatchEvent(new CustomEvent("simple-modal-hide")); + globalThis.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + }), + ); store.appEl.playSound("success"); store.toast( `${site.metadata.site.name} ${op.replace("Site", "")} successful!`, diff --git a/elements/app-hax/lib/v1/app-hax-site-login.js b/elements/app-hax/lib/v1/app-hax-site-login.js index 75e2f6b908..d7350e363d 100644 --- a/elements/app-hax/lib/v1/app-hax-site-login.js +++ b/elements/app-hax/lib/v1/app-hax-site-login.js @@ -202,6 +202,7 @@ export class AppHaxSiteLogin extends SimpleColors { this.dispatchEvent( new CustomEvent("simple-modal-hide", { bubbles: true, + composed: true, cancelable: true, detail: {}, }), diff --git a/elements/app-hax/lib/v1/app-hax-toast.js b/elements/app-hax/lib/v1/app-hax-toast.js index 6490849fbf..ba7dd91c7b 100644 --- a/elements/app-hax/lib/v1/app-hax-toast.js +++ b/elements/app-hax/lib/v1/app-hax-toast.js @@ -1,6 +1,6 @@ import { autorun, toJS } from "mobx"; import { store } from "./AppHaxStore.js"; -import { RPGCharacterToast } from "../rpg-character-toast/rpg-character-toast.js"; +import { RPGCharacterToast } from "@haxtheweb/haxcms-elements/lib/core/ui/rpg-character-toast/rpg-character-toast.js"; export class AppHaxToast extends RPGCharacterToast { static get tag() { diff --git a/elements/app-hax/lib/v1/app-hax-wired-toggle.js b/elements/app-hax/lib/v1/app-hax-wired-toggle.js index 8f7dc89942..8ce5141b62 100644 --- a/elements/app-hax/lib/v1/app-hax-wired-toggle.js +++ b/elements/app-hax/lib/v1/app-hax-wired-toggle.js @@ -1,7 +1,7 @@ import { autorun, toJS } from "mobx"; import { html } from "lit"; import { store } from "./AppHaxStore.js"; -import { WiredDarkmodeToggle } from "../wired-darkmode-toggle/wired-darkmode-toggle.js"; +import { WiredDarkmodeToggle } from "@haxtheweb/haxcms-elements/lib/core/ui/wired-darkmode-toggle/wired-darkmode-toggle.js"; import { SimpleTourFinder } from "@haxtheweb/simple-popover/lib/SimpleTourFinder.js"; export class AppHAXWiredToggle extends SimpleTourFinder(WiredDarkmodeToggle) { diff --git a/elements/app-hax/lib/v2/AppHaxBackendAPI.js b/elements/app-hax/lib/v2/AppHaxBackendAPI.js new file mode 100644 index 0000000000..e108dfdf10 --- /dev/null +++ b/elements/app-hax/lib/v2/AppHaxBackendAPI.js @@ -0,0 +1,265 @@ +import { LitElement, html } from "lit"; +import { localStorageGet } from "@haxtheweb/utils/utils.js"; +import "@haxtheweb/jwt-login/jwt-login.js"; +import { toJS, autorun } from "mobx"; +import { store } from "./AppHaxStore.js"; +import { SimpleColorsSharedStylesGlobal } from "@haxtheweb/simple-colors-shared-styles/simple-colors-shared-styles.js"; +import { SimpleIconIconsetsManifest } from "@haxtheweb/simple-icon/lib/simple-iconset-manifest.js"; +// this element will manage all connectivity to the backend +// this way everything is forced to request through calls to this +// so that it doesn't get messy down below in state +export class AppHaxBackendAPI extends LitElement { + static get tag() { + return "app-hax-backend-api"; + } + + constructor() { + super(); + this.jwt = localStorageGet("jwt", null); + this.method = + window && globalThis.appSettings && globalThis.appSettings.demo + ? "GET" + : "POST"; + this.basePath = "/"; + this.lastResponse = {}; + this.appSettings = {}; + autorun(() => { + this.appSettings = toJS(store.appSettings); + // allow setting in session driven environments + if (this.appSettings.method) { + this.method = this.appSettings.method; + } + if (this.appSettings.jwt) { + this.jwt = this.appSettings.jwt; + } + }); + autorun(() => { + this.token = toJS(store.token); + }); + } + + static get properties() { + return { + jwt: { type: String }, + basePath: { type: String, attribute: "base-path" }, + appSettings: { type: Object }, + method: { type: String }, + token: { type: String }, + }; + } + + render() { + return html``; + } + + // failed to get valid JWT, wipe current + jwtFailed(e) { + this.jwt = null; + this.token = null; + } + // event meaning we either got or removed the jwt + async jwtChanged(e) { + this.jwt = e.detail.value; + // sanity check we actually got a response + // this fires every time our JWT changes so it can update even after already logging in + // like hitting refresh or coming back to the app + if (!this.__loopBlock && this.jwt) { + this.__loopBlock = true; + const userData = await this.makeCall("getUserDataPath"); + if (userData && userData.data) { + store.user = { + name: userData.data.userName, + }; + this.__loopBlock = false; + } else { + // getUserData failed - JWT is invalid/expired + // Clear it and let the logout handler trigger login modal + this.__loopBlock = false; + this.jwt = null; + store.jwt = null; + } + } + } + + async makeCall(call, data = {}, save = false, callback = false) { + if (this.appSettings && this.appSettings[call]) { + var urlRequest = `${this.basePath}${this.appSettings[call]}`; + var options = { + method: this.method, + }; + if (this.jwt) { + data.jwt = this.jwt; + } + if (this.token) { + data.token = this.token; + } + // encode in search params or body of the request + if (this.method === "GET") { + urlRequest += "?" + new URLSearchParams(data).toString(); + } else { + options.body = JSON.stringify(data); + } + const response = await fetch(`${urlRequest}`, options).then( + (response) => { + if (response.ok) { + return response.json(); + } else if (response.status === 401) { + // call not allowed, log out bc unauthorized + globalThis.dispatchEvent( + new CustomEvent("jwt-login-logout", { + composed: true, + bubbles: true, + cancelable: false, + detail: true, + }), + ); + } + // we got a miss, logout cause something is wrong + else if (response.status === 404) { + // call not allowed, log out bc unauthorized + globalThis.dispatchEvent( + new CustomEvent("jwt-login-logout", { + composed: true, + bubbles: true, + cancelable: false, + detail: true, + }), + ); + } else if (response.status === 403) { + // if this was a 403 it should be because of a bad jwt + // or out of date one. let's kick off a call to get a new one + // hopefully from the timing token, knowing this ALSO could kick + // over here. + globalThis.dispatchEvent( + new CustomEvent("jwt-login-refresh-token", { + composed: true, + bubbles: true, + cancelable: false, + detail: { + element: { + obj: this, + callback: "refreshRequest", + params: [call, data, save, callback], + }, + }, + }), + ); + } + return {}; + }, + ); + // ability to save the output if this is being done as a bg task + // that way we can get access to the result later on + if (save) { + this.lastResponse[call] = response; + } + if (callback) { + callback(); + } + return response; + } + } + + /** + * Attempt to salvage the request that was kicked off + * when our JWT needed refreshed + */ + refreshRequest(jwt, response) { + const { call, data, save, callback } = response; + // force the jwt to be the updated jwt + // this helps avoid any possible event timing issue + if (jwt) { + this.jwt = jwt; + this.makeCall(call, data, save, callback); + } + } + + // set instance of API in store + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + // set store refernece to this singleton + store.AppHaxAPI = this; + // site creation roped into the promise list + // after knowing our data structure since we'll definitely call this + store.newSitePromiseList = [ + ...store.newSitePromiseList, + async () => + await this.makeCall("createSite", this._formatSitePostData(), true), + ]; + } + // just easier to read here + _formatSitePostData() { + const site = toJS(store.site); + // html contents if we are starting from a file import, otherwise its null + const items = toJS(store.items); + const itemFiles = toJS(store.itemFiles); + const colors = Object.keys(SimpleColorsSharedStylesGlobal.colors); + const buildData = { + site: { + name: site.name, + description: `${site.type} ${site.structure}`, + theme: site.theme, + }, + build: { + type: site.type, + structure: site.structure, + items: items, + files: itemFiles, + }, + theme: { + // select a random color + color: colors[Math.floor(Math.random() * colors.length)], + // select a random av icon + icon: `${SimpleIconIconsetsManifest[0].name}:${ + SimpleIconIconsetsManifest[0].icons[ + Math.floor( + Math.random() * SimpleIconIconsetsManifest[0].icons.length, + ) + ] + }`, + }, + }; + return buildData; + } + + updated(changedProperties) { + if (super.updated) { + super.updated(changedProperties); + } + changedProperties.forEach((oldValue, propName) => { + if (propName === "jwt") { + store.jwt = this[propName]; + } + if (propName === "token") { + store.token = this[propName]; + } + }); + } +} + +globalThis.AppHaxAPI = globalThis.AppHaxAPI || {}; + +globalThis.AppHaxAPI.requestAvailability = () => { + if (!globalThis.AppHaxAPI.instance) { + globalThis.AppHaxAPI.instance = globalThis.document.createElement( + AppHaxBackendAPI.tag, + ); + globalThis.document.body.appendChild(globalThis.AppHaxAPI.instance); + } + return globalThis.AppHaxAPI.instance; +}; +export const AppHaxAPI = globalThis.AppHaxAPI.requestAvailability(); + +customElements.define(AppHaxBackendAPI.tag, AppHaxBackendAPI); diff --git a/elements/app-hax/lib/v2/AppHaxRouter.js b/elements/app-hax/lib/v2/AppHaxRouter.js new file mode 100644 index 0000000000..169c3758fa --- /dev/null +++ b/elements/app-hax/lib/v2/AppHaxRouter.js @@ -0,0 +1,81 @@ +import { Router } from "@vaadin/router"; +import { autorun, toJS } from "mobx"; +import { store } from "./AppHaxStore.js"; + +/** + * `app-hax-router` + */ +export class AppHaxRouter extends HTMLElement { + /** + * Store the tag name to make it easier to obtain directly. + */ + + static get tag() { + return "app-hax-router"; + } + /** + * ready life cycle + */ + + constructor() { + super(); + // create router + const options = {}; + if (this.baseURI) { + options.baseUrl = this.baseURI; + } + this.windowControllers = new AbortController(); + this.router = new Router(this, options); + autorun(() => { + this._updateRouter(toJS(store.routes)); + }); + autorun(() => { + const manifest = toJS(store.manifest); + const baseURI = toJS(store.AppHaxAPI.basePath); + if (manifest && manifest.items && manifest.items.length > 0) { + const siteItemRoutes = manifest.items.map((i) => { + return { + path: i.slug.replace(baseURI, ""), // replacement of the basePath ensures routes match in haxiam / subdirs + slug: i.slug, + name: i.id, + component: `fake-${i.id}-e`, + }; + }); + store.routes = [...siteItemRoutes].concat(store.baseRoutes); + } + }); + } + + connectedCallback() { + globalThis.addEventListener( + "vaadin-router-location-changed", + this._routerLocationChanged.bind(this), + { signal: this.windowControllers.signal }, + ); + } + /** + * Detached life cycle + */ + + disconnectedCallback() { + this.windowControllers.abort(); + } + + /** + * Update the router based on a manifest. + */ + _updateRouter(routerItems) { + this.router.setRoutes([...routerItems]); + } + /** + * React to page changes in the vaadin router and convert it + * to a change in the mobx store. + * @param {event} e + */ + + // eslint-disable-next-line class-methods-use-this + _routerLocationChanged(e) { + store.location = e.detail.location; + } +} +customElements.define(AppHaxRouter.tag, AppHaxRouter); diff --git a/elements/app-hax/lib/v2/AppHaxStore.js b/elements/app-hax/lib/v2/AppHaxStore.js new file mode 100644 index 0000000000..3f20bfda06 --- /dev/null +++ b/elements/app-hax/lib/v2/AppHaxStore.js @@ -0,0 +1,340 @@ +/* eslint-disable max-classes-per-file */ +import { localStorageGet, localStorageSet } from "@haxtheweb/utils/utils.js"; +import { observable, makeObservable, computed, configure } from "mobx"; +import { DeviceDetails } from "@haxtheweb/replace-tag/lib/PerformanceDetect.js"; +configure({ enforceActions: false }); // strict mode off + +class Store { + constructor() { + this.badDevice = null; + this.evaluateBadDevice(); + this.location = null; + this.token = + globalThis.appSettings && globalThis.appSettings.token + ? globalThis.appSettings.token + : null; + this.version = "0.0.0"; + this.items = null; + this.itemFiles = null; + this.refreshSiteList = true; + this.createSiteSteps = false; + fetch(new URL("../../../haxcms-elements/package.json", import.meta.url)) + .then((response) => response.json()) + .then((obj) => (this.version = obj.version)); + this.appSettings = globalThis.appSettings || {}; + // defer to local if we have it for JWT + if (this.appSettings.jwt) { + localStorageSet("jwt", this.appSettings.jwt); + } + this.jwt = localStorageGet("jwt", null); + // placeholder for when the actual API Backend gets plugged in here + this.AppHaxAPI = {}; + this.newSitePromiseList = [ + () => import("@haxtheweb/i18n-manager/lib/I18NMixin.js"), + () => import("@haxtheweb/wc-autoload/wc-autoload.js"), + () => import("@haxtheweb/replace-tag/replace-tag.js"), + () => import("@haxtheweb/utils/utils.js"), + () => import("@haxtheweb/grid-plate/grid-plate.js"), + () => import("@haxtheweb/simple-fields/simple-fields.js"), + () => import("mobx/dist/mobx.esm.js"), + () => import("@haxtheweb/h-a-x/h-a-x.js"), + () => import("@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"), + () => import("@haxtheweb/haxcms-elements/lib/core/haxcms-site-router.js"), + () => + import("@haxtheweb/haxcms-elements/lib/core/haxcms-site-builder.js"), + () => + import("@haxtheweb/haxcms-elements/lib/core/HAXCMSLitElementTheme.js"), + () => import("@haxtheweb/haxcms-elements/lib/core/haxcms-site-editor.js"), + () => + import("@haxtheweb/haxcms-elements/lib/core/haxcms-editor-builder.js"), + () => + import("@haxtheweb/haxcms-elements/lib/core/haxcms-site-editor-ui.js"), + ]; + this.appEl = null; + this.appReady = false; + this.soundStatus = localStorageGet("app-hax-soundStatus", true); + // If user is new, make sure they are on step 1 + this.appMode = "search"; + this.activeSiteOp = null; + this.activeSiteId = null; + this.baseRoutes = [ + { + path: "createSite-step-1", + component: "fake", + step: 1, + name: "step-1", + label: "New Journey", + statement: "What sort of journey is it?", + title: "Step 1: Create", + }, + { + path: "createSite-step-2", + component: "fake", + step: 2, + name: "step-2", + label: "Structure", + statement: "How is the :structure organized?", + title: "Step 2: Structure", + }, + { + path: "createSite-step-3", + component: "fake", + step: 3, + name: "step-3", + label: "Theme select", + statement: "What your :structure feels like?", + title: "Step 3: Theme", + }, + { + path: "createSite-step-4", + component: "fake", + step: 4, + name: "step-4", + label: "Name", + statement: "What do you want to call your site?", + title: "Step 4: Name", + }, + { + path: "createSite-step-5", + component: "fake", + step: 5, + name: "step-5", + label: "Building..", + statement: "Getting your :structure ready to launch", + title: "Step 5: Building site", + }, + { + path: "home", + component: "fake", + name: "home", + label: "Welcome back", + statement: "Let's go on a HAX Journey", + title: "Home", + }, + { + path: "index.html", + component: "fake", + name: "home", + label: "Welcome back", + statement: "Let's go on a HAX Journey", + title: "Home", + }, + { + path: "index.php", + component: "fake", + name: "home", + label: "Welcome back", + statement: "Let's go on a HAX Journey", + title: "Home", + }, + { + path: "search", + component: "fake", + name: "search", + label: "Search", + statement: "Discover active adventures", + title: "Search sites", + }, + { + path: "/", + component: "fake", + name: "welcome", + label: "Welcome", + statement: "Let's build something awesome!", + title: "Home", + }, + { + path: "/(.*)", + component: "fake", + name: "404", + label: "404 :[", + statement: "it's not you.. it's me", + title: "FoUr Oh FoUr", + }, + ]; + this.routes = this.baseRoutes; + this.siteReady = false; + this.manifest = {}; + this.searchTerm = ""; + this.themesData = {}; + this.user = { + name: "", + }; + this.site = !localStorageGet("app-hax-site") + ? { structure: null, type: null, theme: null, name: null } + : localStorageGet("app-hax-site"); + this.step = this.stepTest(null); + this.darkMode = !localStorageGet("app-hax-darkMode") + ? false + : localStorageGet("app-hax-darkMode"); + + makeObservable(this, { + // internal state for routing + location: observable.ref, // router location in url + routes: observable, // routes that are valid + // internal state requirements + appSettings: observable, // endpoint connections to the backend app + appReady: observable, // all ready to paint + appMode: observable, // mode the app is in. search, create, etc + createSiteSteps: observable, // if we're making a site or in another part of app + step: observable, // step that we're on in our build + site: observable, // information about the site being created + newSitePromiseList: observable, + items: observable, // site items / structure from a docx micro if option selected + itemFiles: observable, // files related to the items to be imported from another site format + version: observable, // version of haxcms FRONTEND as per package.json + // user related data + jwt: observable, // JSON web token + token: observable, // XSS prevention token + manifest: observable, // sites the user has access to + user: observable, // user object like name after login + // user preferences + searchTerm: observable, // current search term for filtering own list of sites + themesData: observable, // themes.json data for theme thumbnails + darkMode: observable, // dark mode pref + soundStatus: observable, // toggle sounds on and off + activeItem: computed, // active item is route + isNewUser: computed, // if they are new so we can auto kick to createSiteSteps if needed + isLoggedIn: computed, // basic bool for logged in + badDevice: observable, // if we have a terrible device or not based on detected speeds + activeSiteOp: observable, // active operation for sites if working with them + activeSiteId: observable, // active Item if working w/ sites + activeSite: computed, // activeSite from ID + siteReady: observable, // implied that we had a site and then it got built and we can leave app + refreshSiteList: observable, // used to force state to refresh sitelisting + }); + } + setPageTitle(title) { + if (globalThis.document.querySelector("title")) { + globalThis.document.querySelector("title").innerText = `HAX: ${title}`; + } + } + // refresh + refreshSiteListing() { + this.refreshSiteList = false; + // @todo this causes a reactive feedbackloop in + this.refreshSiteList = true; + } + // filter to just get data about THIS site + get activeSite() { + if (this.activeSiteId && this.manifest && this.manifest.items) { + const sites = this.manifest.items.filter( + (item) => item.id === this.activeSiteId, + ); + if (sites.length === 1) { + return sites.pop(); + } + return null; + } + } + // see if this device is poor + async evaluateBadDevice() { + this.badDevice = await DeviceDetails.badDevice(); + if (this.badDevice === true) { + this.soundStatus = false; + } + } + // validate if they are on the right step via state + // otherwise we need to force them to the correct step + stepTest(current) { + if (this.site.structure === null && current !== 1) { + return 1; + } else if ( + this.site.structure !== null && + this.site.type === null && + current !== 2 + ) { + return 2; + } else if ( + this.site.structure !== null && + this.site.type !== null && + this.site.theme === null && + current !== 3 + ) { + return 3; + } else if ( + this.site.structure !== null && + this.site.type !== null && + this.site.theme !== null && + this.site.name === null && + current !== 4 + ) { + return 4; + } else if ( + this.site.structure !== null && + this.site.type !== null && + this.site.theme !== null && + this.site.name !== null + ) { + return 5; + } + return current; + } + + get isLoggedIn() { + if (this.appReady && this.AppHaxAPI) { + return this.jwt !== "null" && this.jwt; + } + } + + get isNewUser() { + if (this.manifest && this.manifest.items) { + return this.manifest.items.length === 0; + } + } + + // site{ structure, type, theme } (course, portfolio, buz, colors) + get activeItem() { + if (this.routes.length > 0 && this.location && this.location.route) { + if (this.createSiteSteps) { + const routeItem = this.routes.find((item) => { + if (item.step === undefined || item.step !== this.step) { + return false; + } + return true; + }); + return routeItem; + } else { + return this.location.route; + } + } + } + + // load themes data for theme thumbnails + async loadThemesData() { + if (Object.keys(this.themesData).length === 0) { + try { + const themesUrl = new URL( + "../../../haxcms-elements/lib/themes.json", + import.meta.url, + ).href; + const response = await fetch(themesUrl); + if (response.ok) { + this.themesData = await response.json(); + } + } catch (error) { + console.warn("Failed to load themes data:", error); + } + } + } + + // centralize toast messages + toast(msg, duration = 3000, extras = {}) { + globalThis.dispatchEvent( + new CustomEvent("haxcms-toast-show", { + bubbles: true, + cancelable: true, + composed: true, + detail: { + text: msg, + duration: duration, + ...extras, + }, + }), + ); + } +} +/** + * Central store + */ +export const store = new Store(); diff --git a/elements/app-hax/lib/v2/HAXIAM_API_SPEC.md b/elements/app-hax/lib/v2/HAXIAM_API_SPEC.md new file mode 100644 index 0000000000..3a90925ed4 --- /dev/null +++ b/elements/app-hax/lib/v2/HAXIAM_API_SPEC.md @@ -0,0 +1,126 @@ +# HAXiam API Endpoint Specification + +## User Access Management Endpoint + +### Endpoint: POST /haxiamAddUserAccess + +This endpoint allows users to grant access to their HAXiam sites by creating symlinks between user directories when the target username exists. + +**Availability**: This endpoint is only available when HAXiam mode is enabled (`config->iam = true`). The endpoint will not be present in `appSettings` if HAXiam is not enabled. + +### Request + +**Method:** POST +**URL:** `/haxiamAddUserAccess` + +**Headers:** +- `Content-Type: application/json` +- `Authorization: Bearer {jwt_token}` + +**Request Body:** +```json +{ + "userName": "string", // Required: Username to grant access to + "siteName": "string" // Required: Site name/directory name +} +``` + +### Response + +#### Success Response (200 OK) +```json +{ + "status": "success", + "message": "User access granted successfully", + "username": "targetuser", + "timestamp": "2025-10-02T14:45:00Z" +} +``` + +#### Error Responses + +**403 Forbidden - User Not Found** +```json +{ + "status": "error", + "message": "User not found or unauthorized", + "error_code": "USER_NOT_FOUND" +} +``` + +**400 Bad Request - Invalid Input** +```json +{ + "status": "error", + "message": "userName is required", + "error_code": "INVALID_INPUT" +} +``` + +**400 Bad Request - HAXiam Not Enabled** +```json +{ + "status": "error", + "message": "HAXIAM mode is not enabled", + "error_code": "HAXIAM_DISABLED" +} +``` + +**401 Unauthorized - Invalid Token** +```json +{ + "status": "error", + "message": "Invalid or expired authentication token", + "error_code": "UNAUTHORIZED" +} +``` + +**500 Internal Server Error - System Error** +```json +{ + "status": "error", + "message": "Failed to create user access", + "error_code": "SYSTEM_ERROR" +} +``` + +### Implementation Notes + +1. **HAXcms-PHP Hooks System**: This endpoint will be implemented via the hooks system in HAXcms-PHP, not in the Node.js or typical PHP environment. + +2. **Symlink Creation**: The endpoint creates a symlink between user directories to grant access. The symlink will point from the target user's directory to the requesting user's site. + +3. **User Validation**: The system must verify that the target username exists in the HAXiam system before creating the symlink. + +4. **Security**: + - JWT authentication required + - Validate requesting user has permission to grant access to the specified site + - Prevent directory traversal attacks + - Log all access grant attempts + +5. **Directory Structure**: Assumes HAXiam uses a directory structure where each user has their own folder and symlinks can be created to share access. + +### Frontend Integration + +The frontend conditionally shows the "User Access" menu option only when the `haxiamAddUserAccess` endpoint is present in `appSettings` (indicating HAXiam is enabled). + +The modal (`app-hax-user-access-modal.js`) calls this endpoint via the secure AppHaxAPI system: + +```javascript +const response = await store.AppHaxAPI.makeCall("haxiamAddUserAccess", { + userName: inputUsername, + siteName: siteName +}); +``` + +**Menu Visibility Logic**: The "User Access" button in the site options menu is conditionally rendered: +```javascript +${store.appSettings && store.appSettings.haxiamAddUserAccess ? + html`` : ""} +``` + +### Toast Notifications + +- **Success (200)**: Shows success toast with RPG character matching the added username +- **User Not Found (403)**: Shows error message "User not found or unauthorized" +- **Other Errors**: Shows generic error message with status information \ No newline at end of file diff --git a/elements/app-hax/lib/v2/app-hax-button.js b/elements/app-hax/lib/v2/app-hax-button.js new file mode 100644 index 0000000000..4b6091a76b --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-button.js @@ -0,0 +1,315 @@ +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import "@haxtheweb/hax-iconset/lib/simple-hax-iconset.js"; +import "wired-elements/lib/wired-button.js"; +import { html, css, LitElement } from "lit"; +const postIt = new URL("../assets/images/PostIt.svg", import.meta.url).href; +const betaPostIt = new URL("../assets/images/BetaPostIt.svg", import.meta.url) + .href; + +export class AppHaxButton extends LitElement { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-button"; + } + + // heeyyyyyyyy heeeyyyyyy + constructor() { + super(); + this.icon = "save"; + this.type = null; + this.value = null; + this.disabled = false; + this.elevation = 2; + this.active = false; + this.comingSoon = false; + this.prompt = null; + this.callback = null; + this.param = null; + this.beta = false; + this.addEventListener("click", this._handleClick); + this.addEventListener("click", this._handleClick); + this.addEventListener("focus", this._handleFocus); + this.addEventListener("blur", this._handleBlur); + this.addEventListener("mouseover", this._handleFocus); + this.addEventListener("mouseout", this._handleBlur); + } + + _handleFocus() { + if (!this.disabled && !this.comingSoon) { + this.active = true; + this.elevation = "4"; + } + } + + _handleBlur() { + if (!this.disabled && !this.comingSoon) { + this.active = false; + this.elevation = "2"; + } + } + + _handleClick() { + if (!this.disabled && !this.comingSoon) { + this.shadowRoot.querySelector(".haxButton").blur(); + } + } + + static get properties() { + return { + icon: { type: String }, + type: { type: String, reflect: true }, + disabled: { type: Boolean, reflect: true }, + elevation: { type: Number }, + active: { type: Boolean, reflect: true }, + comingSoon: { type: Boolean, reflect: true, attribute: "coming-soon" }, + beta: { type: Boolean, reflect: true }, + prompt: { type: String }, + callback: { type: String }, + param: { type: String }, + }; + } + + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + changedProperties.forEach((oldValue, propName) => { + if (propName === "type") { + switch (this.type) { + case "technology": + this.icon = "hardware:desktop-mac"; + this.value = "technology"; + break; + case "business": + this.icon = "maps:local-atm"; + this.value = "business"; + break; + case "art": + this.icon = "image:palette"; + this.value = "art"; + break; + case "6w": + this.icon = "hax:messages-6"; + this.value = "6 Week"; + break; + case "15w": + this.icon = "social:school"; + this.value = "15 Week"; + break; + case "training": + this.icon = "hax:bricks"; + this.value = "Training"; + break; + case "docx import": + this.icon = "hax:file-docx"; + this.value = "docx"; + break; + case "docx": + this.icon = "hax:file-docx"; + this.value = "docx"; + break; + case "evolution": + this.icon = "communication:business"; + this.value = "evo"; + break; + case "pressbooks": + this.icon = "hax:wordpress"; + this.value = "pressbooks"; + break; + case "gitbook": + this.icon = "mdi-social:github-circle"; + this.value = "gitbook"; + break; + case "elms:ln": + this.icon = "lrn:network"; + this.value = "elmsln"; + break; + case "haxcms": + this.icon = "hax:hax2022"; + this.value = "haxcms"; + break; + case "notion": + this.icon = "book"; + this.value = "notion"; + break; + case "html": + this.icon = "icons:code"; + this.value = "HTML"; + break; + case "Blog": + this.icon = "social:public"; + this.value = "Blog"; + break; + default: + this.icon = "image:photo-filter"; + this.value = "own"; + this.type = "Create Your Own"; + break; + } + } + }); + } + + static get styles() { + return [ + css` + :host { + display: block; + --background-color: transparent; + --background-color-active: white; + } + :host([coming-soon]) .haxButton { + pointer-events: none; + background-color: var(--simple-colors-default-theme-grey-6); + } + :host([active]) .haxButton { + color: var( + --app-hax-background-color, + var(--background-color-active) + ); + background-color: var(--app-hax-accent-color, var(--accent-color)); + } + :host([active]) simple-icon-lite { + --simple-icon-color: var( + --app-hax-background-color, + var(--background-color-active) + ); + } + :host([active]) .type { + background-color: var(--app-hax-accent-color, var(--accent-color)); + color: var( + --app-hax-background-color, + var(--background-color-active) + ); + } + + #container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + width: 132px; + height: 112px; + } + .coming-soon { + display: block; + height: 114px; + width: 140px; + z-index: 1; + position: absolute; + margin-top: -75px; + } + .beta { + display: block; + height: 100px; + width: 120px; + z-index: 1; + position: absolute; + top: 0; + left: 0; + margin-left: -50px; + margin-top: -10px; + } + .haxButton { + background-color: var( + --app-hax-background-color, + var(--background-color) + ); + color: var(--app-hax-accent-color, var(--accent-color)); + display: inline-flex; + } + simple-icon-lite { + --simple-icon-width: 60px; + --simple-icon-height: 60px; + --simple-icon-color: var(--app-hax-accent-color, var(--accent-color)); + } + .type { + font-size: 10px; + color: var(--app-hax-accent-color, var(--accent-color)); + } + @media (max-width: 800px) { + #container { + width: 100px; + height: 75px; + } + + .beta, + .coming-soon { + margin-top: -50px; + height: 114px; + width: 100px; + } + } + + /* Screen reader only text */ + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + `, + ]; + } + + render() { + return html` + +
+ + +
${this.type}
+
+ ${this.comingSoon + ? html`` + : ``} + ${this.beta + ? html`` + : ``} +
+ ${this.type + ? html` +
+ ${this.type} content type option +
+ ` + : ""} + `; + } +} +customElements.define(AppHaxButton.tag, AppHaxButton); diff --git a/elements/app-hax/lib/v2/app-hax-confirmation-modal.js b/elements/app-hax/lib/v2/app-hax-confirmation-modal.js new file mode 100644 index 0000000000..06051d4d1b --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-confirmation-modal.js @@ -0,0 +1,280 @@ +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import "@haxtheweb/simple-modal/simple-modal.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import "@haxtheweb/simple-icon/lib/simple-icons.js"; +import { store } from "./AppHaxStore.js"; + +export class AppHaxConfirmationModal extends DDDSuper(LitElement) { + static get tag() { + return "app-hax-confirmation-modal"; + } + + constructor() { + super(); + this.open = false; + this.title = ""; + this.message = ""; + this.confirmText = "Confirm"; + this.cancelText = "Cancel"; + this.confirmAction = null; + this.cancelAction = null; + this.dangerous = false; // For destructive actions like delete/archive + } + + static get properties() { + return { + open: { type: Boolean, reflect: true }, + title: { type: String }, + message: { type: String }, + confirmText: { type: String }, + cancelText: { type: String }, + dangerous: { type: Boolean }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + --simple-modal-z-index: 10000; + --simple-modal-width: var(--ddd-spacing-32, 480px); + --simple-modal-max-width: 90vw; + --simple-modal-max-height: 80vh; + --simple-modal-border-radius: var(--ddd-radius-md, 8px); + --simple-modal-titlebar-background: var( + --ddd-theme-default-nittanyNavy, + #001e44 + ); + --simple-modal-titlebar-color: var(--ddd-theme-default-white, white); + --simple-modal-content-padding: var(--ddd-spacing-6, 24px); + --simple-modal-content-container-background: var( + --ddd-theme-default-white, + white + ); + --simple-modal-background: var(--ddd-theme-default-white, white); + --simple-modal-box-shadow: var(--ddd-boxShadow-xl); + --simple-modal-buttons-padding: 0; + font-family: var(--ddd-font-primary, sans-serif); + } + + :host([dangerous]) { + --simple-modal-titlebar-background: var( + --ddd-theme-default-original87Pink, + #e4007c + ); + } + + simple-modal { + font-family: var(--ddd-font-primary, sans-serif) !important; + } + + simple-modal::part(title) { + font-family: var(--ddd-font-primary, sans-serif) !important; + font-size: var(--ddd-font-size-m, 18px) !important; + font-weight: var(--ddd-font-weight-bold, 700) !important; + } + + .modal-content { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + min-height: var(--ddd-spacing-16, 120px); + } + + .message { + font-size: var(--ddd-font-size-s, 16px); + color: var(--ddd-theme-default-coalyGray, #444); + margin: 0 0 var(--ddd-spacing-6, 24px) 0; + line-height: var(--ddd-lh-140, 1.4); + } + + .button-group { + display: flex; + gap: var(--ddd-spacing-3, 12px); + justify-content: center; + width: 100%; + } + + .button { + padding: var(--ddd-spacing-3, 12px) var(--ddd-spacing-4, 16px); + border-radius: var(--ddd-radius-sm, 4px); + font-size: var(--ddd-font-size-xs, 14px); + font-weight: var(--ddd-font-weight-medium, 500); + font-family: var(--ddd-font-primary, sans-serif); + cursor: pointer; + transition: all 0.2s ease; + border: none; + display: flex; + align-items: center; + justify-content: center; + gap: var(--ddd-spacing-2, 8px); + min-width: var(--ddd-spacing-20, 80px); + } + + .button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .button-confirm { + background: var(--ddd-theme-default-nittanyNavy, #001e44) !important; + color: var(--ddd-theme-default-white, white) !important; + } + + .button-confirm:hover:not(:disabled) { + background: var( + --ddd-theme-default-keystoneYellow, + #ffd100 + ) !important; + color: var(--ddd-theme-default-nittanyNavy, #001e44) !important; + } + + :host([dangerous]) .button-confirm { + background: var( + --ddd-theme-default-original87Pink, + #e4007c + ) !important; + } + + :host([dangerous]) .button-confirm:hover:not(:disabled) { + background: var( + --ddd-theme-default-original87Pink-dark, + #c4006c + ) !important; + } + + .button-cancel { + background: transparent !important; + border: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-nittanyNavy, #001e44) !important; + } + + .button-cancel:hover:not(:disabled) { + background: var(--ddd-theme-default-nittanyNavy, #001e44) !important; + } + + @media (max-width: 600px) { + .button-group { + flex-direction: column; + gap: var(--ddd-spacing-2, 8px); + } + + .button { + width: 100%; + min-height: var(--ddd-spacing-10, 40px); + } + } + `, + ]; + } + + openModal() { + // Prevent body scrolling while modal is open + document.body.style.overflow = "hidden"; + + this.open = true; + const modal = this.shadowRoot.querySelector("simple-modal"); + if (modal) { + modal.opened = true; + } + } + + closeModal() { + // Restore body scrolling + document.body.style.overflow = ""; + + this.open = false; + const modal = this.shadowRoot.querySelector("simple-modal"); + if (modal) { + modal.opened = false; + } + + // Removed sound effects for modal close/cancel as requested + + if (this.cancelAction && typeof this.cancelAction === "function") { + this.cancelAction(); + } + // Dispatch close event for cleanup + this.dispatchEvent( + new CustomEvent("close", { + bubbles: true, + composed: true, + }), + ); + } + + handleModalClosed(e) { + // Restore body scrolling + document.body.style.overflow = ""; + + // simple-modal sends close event, we need to sync our state + this.open = false; + + // Removed sound effects for modal close/cancel as requested + + if (this.cancelAction && typeof this.cancelAction === "function") { + this.cancelAction(); + } + // Dispatch close event for cleanup + this.dispatchEvent( + new CustomEvent("close", { + bubbles: true, + composed: true, + }), + ); + } + + confirmModal() { + // Restore body scrolling + document.body.style.overflow = ""; + + this.open = false; + const modal = this.shadowRoot.querySelector("simple-modal"); + if (modal) { + modal.opened = false; + } + + // Play success sound for confirm + if (store.appEl && store.appEl.playSound) { + store.appEl.playSound("success"); + } + + if (this.confirmAction && typeof this.confirmAction === "function") { + this.confirmAction(); + } + // Dispatch close event for cleanup + this.dispatchEvent( + new CustomEvent("close", { + bubbles: true, + composed: true, + }), + ); + } + + render() { + return html` + +
+

${this.message}

+
+
+ + +
+
+ `; + } +} + +customElements.define(AppHaxConfirmationModal.tag, AppHaxConfirmationModal); diff --git a/elements/app-hax/lib/v2/app-hax-filter-tag.js b/elements/app-hax/lib/v2/app-hax-filter-tag.js new file mode 100644 index 0000000000..0f3cc08aa8 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-filter-tag.js @@ -0,0 +1,75 @@ +/* eslint-disable no-return-assign */ +import { LitElement, html, css } from "lit"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; + +export class AppHaxFilterTag extends LitElement { + static get tag() { + return "app-hax-filter-tag"; + } + + constructor() { + super(); + this.label = ""; + } + + static get properties() { + return { + label: { type: String }, + }; + } + + updated(changedProperties) {} + + static get styles() { + return [ + css` + :host { + display: inline-flex; + font-family: var(--ddd-font-primary); + color: var(--simple-colors-default-theme-grey-1, var(--accent-color)); + padding-left: 8px; + padding-right: 8px; + height: 32px; + background-color: var( + --simple-colors-default-theme-light-blue-12, + var(--accent-color) + ); + border-radius: 8px; + font-size: 16px; + align-items: center; + justify-content: flex-start; + } + div { + display: flex; + align-items: center; + } + .remove { + cursor: pointer; + margin-left: 6px; + font-weight: bold; + display: inline-block; + } + `, + ]; + } + + removeTag() { + this.dispatchEvent( + new CustomEvent("remove-tag", { + detail: this.label, + bubbles: true, + composed: true, + }), + ); + } + + render() { + return html` +
+

${this.label}

+ ✖ +
+ `; + } +} +customElements.define(AppHaxFilterTag.tag, AppHaxFilterTag); diff --git a/elements/app-hax/lib/v2/app-hax-hat-progress.js b/elements/app-hax/lib/v2/app-hax-hat-progress.js new file mode 100644 index 0000000000..aff4ce82ed --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-hat-progress.js @@ -0,0 +1,252 @@ +import { html, css } from "lit"; +import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import { autorun, toJS } from "mobx"; +import { store } from "./AppHaxStore.js"; +import "@haxtheweb/promise-progress/promise-progress.js"; + +export class AppHaxHatProgress extends SimpleColors { + static get tag() { + return "app-hax-hat-progress"; + } + + constructor() { + super(); + this.promises = []; + this.max = 100; + autorun(() => { + this.promises = toJS(store.newSitePromiseList); + }); + autorun(() => { + this.dark = toJS(store.darkMode); + }); + } + + static get properties() { + return { + ...super.properties, + promises: { type: Array }, + }; + } + + process() { + this.shadowRoot.querySelector("#progress2").process(); + } + + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + this.dispatchEvent(new CustomEvent("progress-ready", { detail: true })); + + setTimeout(() => { + this.shadowRoot + .querySelector("#progress2") + .addEventListener("value-changed", (e) => { + this.shadowRoot.querySelector("#value").textContent = e.detail.value; + }); + this.shadowRoot + .querySelector("#progress2") + .addEventListener("max-changed", (e) => { + this.max = e.detail.value; + }); + this.shadowRoot + .querySelector("#progress2") + .addEventListener("promise-progress-finished", (e) => { + if (e.detail.value) { + // this will seem like magic... but our createSite + // Promise has a special flag on the function that + // saves the result in an object relative to our API broker + // this way if we ask it for the last thing it created + // the response is there even though we kicked it off previously + // we more or less assume it completed bc the Promises all resolved + // and it was our 1st Promise we asked to issue! + + // state clean up incase activated twice + if (this.shadowRoot.querySelector(".game")) { + this.shadowRoot.querySelector(".game").remove(); + } + + const createResponse = store.AppHaxAPI.lastResponse.createSite.data; + // ensure the dashboard site list reflects the newly created site + // trigger the standard reactive refresh flag + if (store && store.refreshSiteListing) { + store.refreshSiteListing(); + } + // also force a fresh getSitesList call with a cache-busting timestamp + // and slight delay so the backend has time to finish any async writes + if (store && store.AppHaxAPI && store.AppHaxAPI.makeCall) { + setTimeout(async () => { + try { + const results = await store.AppHaxAPI.makeCall( + "getSitesList", + { _t: Date.now() }, + ); + if (results && results.data) { + store.manifest = results.data; + } + } catch (e) { + // non-fatal; worst case the user can manually refresh + } + }, 500); + } + const text = globalThis.document.createElement("button"); + this.shadowRoot.querySelector("#value").textContent = this.max; + text.textContent = "Let's go!"; + text.classList.add("game"); + text.addEventListener("pointerdown", () => { + store.appEl.playSound("click"); + }); + text.addEventListener("click", () => { + store.appEl.reset(); + setTimeout(() => { + globalThis.location = createResponse.slug.replace( + "index.html", + "", + ); + }, 0); + }); + this.shadowRoot + .querySelector("#progress2") + .parentNode.appendChild(text); + // show you saying you got this! + store.toast( + `${createResponse.title ? createResponse.title : ""} ready!`, + 1500, + { + hat: "random", + }, + ); + store.setPageTitle( + `${createResponse.title ? createResponse.title : ""} ready!`, + ); + setTimeout(() => { + store.toast(`redirecting in 3..`, 10000, { + hat: "random", + walking: true, + }); + store.setPageTitle("Redirecting in 3.."); + setTimeout(() => { + store.toast(`redirecting in 2..`, 10000, { + hat: "random", + walking: true, + }); + store.setPageTitle("Redirecting in 2.."); + setTimeout(() => { + store.toast(`redirecting in 1..`, 10000, { + hat: "random", + walking: true, + }); + store.setPageTitle("Redirecting in 1.."); + store.appEl.reset(); + setTimeout(() => { + store.setPageTitle(`Enjoy!`); + globalThis.location = createResponse.slug.replace( + "index.html", + "", + ); + }, 1000); + }, 1000); + }, 1000); + }, 1800); + this.dispatchEvent( + new CustomEvent("promise-progress-finished", { + composed: true, + bubbles: true, + cancelable: true, + detail: true, + }), + ); + } + }); + }, 0); + } + + static get styles() { + return [ + super.styles, + css` + :host { + display: block; + height: 400px; + width: 400px; + } + img { + width: 400px; + height: 400px; + pointer-events: none; + } + .progress { + margin: -148px 0 0 10px; + z-index: -1; + } + .progress::part(progress) { + height: 100px; + width: 338px; + margin-top: -1px 0 0 -4px; + } + + .progress::part(progress)::-moz-progress-bar { + background-color: red; + height: 50px; + margin: 24px 0 0 0; + border: none; + } + + .count { + color: var(--simple-colors-default-theme-grey-1, white); + width: 350px; + text-align: center; + position: relative; + display: block; + font-size: 30px; + margin-top: -250px; + margin-left: 30px; + } + .game { + font-size: 28px; + font-weight: bold; + text-align: center; + width: 310px; + background-color: var(--simple-colors-default-theme-red-7, red); + color: var(--simple-colors-default-theme-grey-1, white); + border: 0px; + height: 54px; + display: block; + position: relative; + margin: 138px 0px 0px 52px; + padding: 0; + box-sizing: border-box; + } + .game:focus, + .game:hover { + cursor: pointer; + background-color: var(--simple-colors-default-theme-red-8); + color: var(--simple-colors-default-theme-grey-2); + } + .game:active { + cursor: progress; + background-color: var(--simple-colors-default-theme-red-10); + color: var(--simple-colors-default-theme-grey-5); + } + `, + ]; + } + + render() { + return html` + + +
0%
+ `; + } +} +customElements.define(AppHaxHatProgress.tag, AppHaxHatProgress); diff --git a/elements/app-hax/lib/v2/app-hax-label.js b/elements/app-hax/lib/v2/app-hax-label.js new file mode 100644 index 0000000000..29163ae260 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-label.js @@ -0,0 +1,117 @@ +// dependencies / things imported +import { LitElement, html, css } from "lit"; + +export class AppHaxLabel extends LitElement { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-label"; + } + + constructor() { + super(); + this.title = "HAX site list"; + this.subtitle = "Let's build something awesome!"; + } + + static get properties() { + return { + title: { type: String }, + subtitle: { type: String }, + }; + } + + // TODO: If scaling is weird with font-sizes, try using clamp() (https://css-tricks.com/linearly-scale-font-size-with-css-clamp-based-on-the-viewport/) + static get styles() { + return css` + :host { + font-family: var(--ddd-font-primary); + text-align: flex-start; + width: 100%; + max-width: 800px; + } + + .topBar { + display: flex; + align-items: baseline; + gap: var(--ddd-spacing-4, 8px); + } + + .title { + -webkit-text-stroke: 1px + var(--app-hax-accent-color, var(--accent-color)); + -webkit-text-fill-color: var( + --app-hax-background-color, + var(--background-color) + ); + font-weight: normal; + font-size: var(--ddd-font-size-l, 1.8vw); + display: inline-flex; + align-items: baseline; + min-width: fit-content; + } + + .subtitle { + color: var(--app-hax-accent-color, var(--accent-color)); + font-weight: normal; + font-size: var(--ddd-font-size-s, 16px); + font-family: var(--ddd-font-secondary, sans-serif); + margin: 0; + flex: 1; + min-width: fit-content; + } + + @media (max-width: 700px) { + .topBar { + flex-direction: column; + align-items: flex-start; + gap: var(--ddd-spacing-1, 4px); + } + .subtitle { + font-size: var(--ddd-font-size-xs, 12px); + } + .title { + font-size: var(--ddd-font-size-s, 2vw); + } + } + + .bracket { + font-size: var(--ddd-font-size-m); + font-weight: normal; + vertical-align: middle; + -webkit-text-stroke: 0px; + -webkit-text-fill-color: var( + --app-hax-accent-color, + var(--accent-color) + ); + } + + @media (max-height: 500px) { + .title { + -webkit-text-stroke: unset; + -webkit-text-fill-color: unset; + } + .bracket { + font-size: var(--ddd-font-size-m); + margin: 0; + padding: 0; + } + } + `; + } + + render() { + return html` +
+

+ < + ${this.title} + > +

+
+ ${this.subtitle} +
+
+ `; + } +} +customElements.define(AppHaxLabel.tag, AppHaxLabel); diff --git a/elements/app-hax/lib/v2/app-hax-scroll-button.js b/elements/app-hax/lib/v2/app-hax-scroll-button.js new file mode 100644 index 0000000000..02490b5365 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-scroll-button.js @@ -0,0 +1,178 @@ +/* eslint-disable no-return-assign */ +import { LitElement, html, css } from "lit"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; + +export class AppHaxScrollButton extends LitElement { + static get tag() { + return "app-hax-scroll-button"; + } + + constructor() { + super(); + this.label = ""; + this.targetId = ""; + this.isDarkMode = document.body.classList.contains("dark-mode"); + this.addEventListener("keydown", this._handleKeydown.bind(this)); + } + + static get properties() { + return { + label: { type: String }, + targetId: { type: String }, + isDarkMode: { type: Boolean, reflect: true }, + }; + } + + scrollToTarget() { + if (this.targetId) { + let targetElement = null; + let appHax = this.closest("app-hax") || document.querySelector("app-hax"); + + if (appHax) { + let useCaseFilter = appHax.shadowRoot + ? appHax.shadowRoot.querySelector("app-hax-use-case-filter") + : appHax.querySelector("app-hax-use-case-filter"); + if (useCaseFilter) { + targetElement = useCaseFilter.shadowRoot + ? useCaseFilter.shadowRoot.getElementById(this.targetId) + : useCaseFilter.querySelector(`#${this.targetId}`); + } + } + if (!targetElement) { + targetElement = document.getElementById(this.targetId); + } + if (targetElement) { + targetElement.scrollIntoView({ behavior: "smooth", block: "start" }); + // Announce to screen readers + this._announceNavigation(); + } else { + console.warn( + `Element with id '${this.targetId}' not found inside app-hax-use-case-filter.`, + ); + } + } + } + connectedCallback() { + super.connectedCallback(); + this._darkModeObserver = new MutationObserver(() => { + this.isDarkMode = document.body.classList.contains("dark-mode"); + }); + this._darkModeObserver.observe(document.body, { + attributes: true, + attributeFilter: ["class"], + }); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this._darkModeObserver) { + this._darkModeObserver.disconnect(); + } + } + + static get styles() { + return [ + css` + :host { + display: flex; + background-image: url("/elements/app-hax/lib/assets/images/scroll-button-LM.png"); + background-size: cover; + background-position: center; + width: 300px; + height: 50px; + color: var(--app-hax-accent-color, var(--accent-color)); + align-items: center; + justify-content: center; + text-align: center; + margin: 8px; + } + :host([isDarkMode]) { + background-image: url("/elements/app-hax/lib/assets/images/scroll-button-DM.png"); + } + div { + display: flex; + align-items: center; + cursor: pointer; + } + + div:hover, + div:focus { + outline: 2px solid var(--ddd-theme-default-keystoneYellow, #ffd100); + outline-offset: 2px; + } + + /* Screen reader only text */ + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + + /* Live region for announcements */ + .live-region { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + } + `, + ]; + } + + render() { + return html` +
+
${this.label}
+
+ Click to scroll to the ${this.label} section +
+
+
+ `; + } + + /** + * Handle keyboard navigation + */ + _handleKeydown(e) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + this.scrollToTarget(); + } + } + + /** + * Announce navigation to screen readers + */ + _announceNavigation() { + const announcement = this.shadowRoot.querySelector("#scroll-announcement"); + if (announcement) { + announcement.textContent = `Navigated to ${this.label} section`; + // Clear announcement after delay + setTimeout(() => { + announcement.textContent = ""; + }, 1000); + } + } +} +customElements.define(AppHaxScrollButton.tag, AppHaxScrollButton); diff --git a/elements/app-hax/lib/v2/app-hax-search-bar.js b/elements/app-hax/lib/v2/app-hax-search-bar.js new file mode 100644 index 0000000000..f22552a821 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-search-bar.js @@ -0,0 +1,163 @@ +/* eslint-disable no-return-assign */ +import { LitElement, html, css } from "lit"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; +import { store } from "./AppHaxStore.js"; + +export class AppHaxSearchBar extends LitElement { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-search-bar"; + } + + constructor() { + super(); + this.searchTerm = ""; + this.disabled = false; + this.showSearch = false; + } + + // Site.json is coming from + + static get properties() { + return { + searchTerm: { type: String }, + showSearch: { type: Boolean, reflect: true, attribute: "show-search" }, + disabled: { type: Boolean, reflect: true }, + }; + } + + updated(changedProperties) { + changedProperties.forEach((oldValue, propName) => { + if (propName === "searchItems") { + this.displayItems = [...this.searchItems]; + } else if (propName === "searchTerm") { + store.searchTerm = this.searchTerm; + } else if (propName === "showSearch" && oldValue !== undefined) { + if (this[propName] === false) { + this.searchTerm = ""; + } + } + }); + } + + static get styles() { + return [ + css` + :host { + overflow: hidden; + position: relative; + display: inline-block; + } + + [role="search"] { + position: relative; + display: inline-block; + } + + .search-input-container { + position: relative; + display: inline-block; + } + input { + opacity: 1; + width: 750px; + transition: all ease-in-out 0.3s; + padding: 4px; + padding-left: 35px; + font-family: sans-serif; + font-size: 16px; + margin: 2px 0 0 16px; + } + @media (max-width: 780px) { + :host([show-search]) input { + width: 250px; + max-width: 20vw; + } + } + @media (max-width: 600px) { + :host([show-search]) input { + width: 200px; + max-width: 20vw; + } + } + + simple-toolbar-button[disabled] { + background-color: #cccccc; + pointer-events: none; + cursor: help; + } + simple-toolbar-button { + min-width: 48px; + margin: 0; + --simple-toolbar-border-color: #dddddddd; + height: 48px; + --simple-toolbar-button-disabled-opacity: 0.3; + --simple-toolbar-button-padding: 3px 6px; + --simple-toolbar-border-radius: 0; + } + simple-toolbar-button:hover, + simple-toolbar-button:active, + simple-toolbar-button:focus { + background-color: var(--hax-ui-background-color-accent); + color: var(--hax-ui-color); + } + simple-toolbar-button:hover, + simple-toolbar-button:active, + simple-toolbar-button:focus { + --simple-toolbar-border-color: var(--hax-ui-color-accent); + } + .search-icon { + position: absolute; + left: 24px; + top: 50%; + transform: translateY(-50%); + font-size: 16px; + align-self: center; + } + `, + ]; + } + testKeydown(e) { + if (e.key === "Escape" || e.key === "Enter") { + this.toggleSearch(); + } + } + // eslint-disable-next-line class-methods-use-this + search() { + this.searchTerm = this.shadowRoot.querySelector("#searchField").value; + } + + render() { + return html` +
+
+ + +
+ +
+ `; + } + + toggleSearch() { + if (!this.disabled) { + this.shadowRoot.querySelector("#searchField").value = ""; + } + } +} +customElements.define(AppHaxSearchBar.tag, AppHaxSearchBar); diff --git a/elements/app-hax/lib/v2/app-hax-search-results.js b/elements/app-hax/lib/v2/app-hax-search-results.js new file mode 100644 index 0000000000..daa350d288 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-search-results.js @@ -0,0 +1,767 @@ +/* eslint-disable no-return-assign */ +import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import { html, css } from "lit"; +import { autorun, toJS } from "mobx"; +import { varGet } from "@haxtheweb/utils/utils.js"; +import { store } from "./AppHaxStore.js"; +import "./app-hax-site-bar.js"; +import "./app-hax-site-details.js"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; + +export class AppHaxSearchResults extends SimpleColors { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-search-results"; + } + + constructor() { + super(); + this.searchItems = []; + this.displayItems = []; + this.searchTerm = ""; + this.dark = false; + this.isPointerDown = false; + this.startX = 0; + this.scrollLeftVal = 0; + this.isDragging = false; + this.currentIndex = 1; + this.totalItems = 0; + this.sortOption = "az"; + autorun(() => { + this.searchTerm = toJS(store.searchTerm); + }); + autorun(() => { + this.dark = toJS(store.darkMode); + }); + autorun(() => { + const manifest = toJS(store.manifest); + if (manifest && manifest.items) { + this.searchItems = manifest.items; + this.displayItems = [...this.searchItems]; + // Ensure themes data is loaded for thumbnails + store.loadThemesData(); + } + }); + } + + // Site.json is coming from + + static get properties() { + return { + ...super.properties, + searchTerm: { type: String, reflect: true }, + searchItems: { type: Array }, + displayItems: { type: Array }, + siteUrl: { type: String, attribute: "site-url" }, + isPointerDown: { type: Boolean }, + startX: { type: Number }, + scrollLeftVal: { type: Number }, + isDragging: { type: Boolean }, + currentIndex: { type: Number }, + totalItems: { type: Number }, + sortOption: { type: String, attribute: "sort-option" }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + display: block; + width: 100%; + overflow: visible; + } + + .carousel-container { + display: flex; + align-items: center; + justify-content: flex-start; + gap: var(--ddd-spacing-2, 8px); + width: 65vw; + max-height: 280px; + position: relative; + border-radius: var(--ddd-radius-md, 8px); + border: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-limestoneLight, #e4e5e7); + box-shadow: var(--ddd-boxShadow-sm); + padding: var(--ddd-spacing-2, 8px); + transition: box-shadow 0.2s ease; + overflow: visible; + } + :host([dark]) .carousel-container, + body.dark-mode .carousel-container { + border-color: var(--ddd-theme-default-slateGray, #666); + color: var(--ddd-theme-default-white, white); + } + .carousel-container:hover { + box-shadow: var(--ddd-boxShadow-md); + } + .pager-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: var(--ddd-spacing-2, 8px); + margin-right: var(--ddd-spacing-4, 16px); + padding: var(--ddd-spacing-1, 4px); + flex-shrink: 0; + scrollbar-width: none; + -ms-overflow-style: none; + } + + .pager-container::-webkit-scrollbar { + display: none; + } + .pager-dot { + width: var(--ddd-spacing-3, 12px); + height: var(--ddd-spacing-3, 12px); + border-radius: var(--ddd-radius-circle, 50%); + background: var(--ddd-theme-default-limestoneGray, #a2aaad); + border: var(--ddd-border-xs, 1px solid) transparent; + cursor: pointer; + transition: all 0.2s ease; + outline: none; + } + .pager-dot:hover, + .pager-dot:focus { + transform: scale(1.2); + outline: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + } + .pager-dot.active { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + border-color: var(--ddd-theme-default-keystoneYellow, #ffd100); + transform: scale(1.1); + } + :host([dark]) .pager-dot, + body.dark-mode .pager-dot { + background: var(--ddd-theme-default-slateGray, #666); + } + :host([dark]) .pager-dot.active, + body.dark-mode .pager-dot.active { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + border-color: var(--ddd-theme-default-white, white); + } + .scroll-left, + .scroll-right { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + border: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + border-radius: var(--ddd-radius-sm, 4px); + padding: var(--ddd-spacing-4, 16px); + cursor: pointer; + height: 240px; + min-width: var(--ddd-spacing-12, 56px); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--ddd-font-size-s, 20px); + transition: all 0.2s ease; + flex-shrink: 0; + z-index: 10; + position: relative; + } + .scroll-right { + margin-right: 0; + } + + :host([dark]) .scroll-left, + :host([dark]) .scroll-right, + body.dark-mode .scroll-left, + body.dark-mode .scroll-right { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + border-color: var(--ddd-theme-default-white, white); + } + + .scroll-left:hover:not(:disabled), + .scroll-right:hover:not(:disabled) { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + transform: translateY(-2px); + box-shadow: var(--ddd-boxShadow-md); + } + + .scroll-left:disabled, + .scroll-right:disabled { + opacity: 0.3; + cursor: not-allowed; + background: var(--ddd-theme-default-limestoneGray, #a2aaad); + color: var(--ddd-theme-default-slateGray, #666); + border-color: var(--ddd-theme-default-limestoneGray, #a2aaad); + transform: none; + box-shadow: none; + } + + :host([dark]) .scroll-left:hover:not(:disabled), + :host([dark]) .scroll-right:hover:not(:disabled), + body.dark-mode .scroll-left:hover:not(:disabled), + body.dark-mode .scroll-right:hover:not(:disabled) { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + } + + :host([dark]) .scroll-left:disabled, + :host([dark]) .scroll-right:disabled, + body.dark-mode .scroll-left:disabled, + body.dark-mode .scroll-right:disabled { + opacity: 0.3; + cursor: not-allowed; + background: var(--ddd-theme-default-coalyGray, #444); + color: var(--ddd-theme-default-slateGray, #666); + border-color: var(--ddd-theme-default-coalyGray, #444); + } + + #results { + display: flex; + flex-wrap: nowrap; + overflow-x: auto; + overflow-y: hidden; + scroll-snap-type: x mandatory; + gap: var(--ddd-spacing-6, 24px); + padding: var(--ddd-spacing-1, 4px) var(--ddd-spacing-2, 8px); + flex: 1; + min-width: 0; + cursor: grab; + user-select: none; + /* Keep scrollbar visible for multiple interaction methods */ + scrollbar-width: thin; + scrollbar-color: var(--ddd-theme-default-slateGray, #666) + var(--ddd-theme-default-limestoneLight, #e4e5e7); + } + + :host([dark]) #results, + body.dark-mode #results { + scrollbar-color: var(--ddd-theme-default-limestoneGray, #a2aaad) + var(--ddd-theme-default-coalyGray, #444); + } + + #results.dragging { + cursor: grabbing; + scroll-behavior: auto; + } + + #results::-webkit-scrollbar { + height: 8px; + } + + #results::-webkit-scrollbar-track { + background: var(--ddd-theme-default-limestoneLight, #e4e5e7); + border-radius: var(--ddd-radius-xs, 2px); + } + + #results::-webkit-scrollbar-thumb { + background: var(--ddd-theme-default-slateGray, #666); + border-radius: var(--ddd-radius-xs, 2px); + transition: background 0.2s ease; + } + + #results::-webkit-scrollbar-thumb:hover { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + } + + :host([dark]) #results::-webkit-scrollbar-track, + body.dark-mode #results::-webkit-scrollbar-track { + background: var(--ddd-theme-default-coalyGray, #444); + } + + :host([dark]) #results::-webkit-scrollbar-thumb, + body.dark-mode #results::-webkit-scrollbar-thumb { + background: var(--ddd-theme-default-limestoneGray, #a2aaad); + } + + :host([dark]) #results::-webkit-scrollbar-thumb:hover, + body.dark-mode #results::-webkit-scrollbar-thumb:hover { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + } + + li { + flex: 0 0 auto; + scroll-snap-align: center; + width: 220px; + min-width: 220px; + height: 260px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + } + + app-hax-site-bar { + margin: 0 var(--ddd-spacing-3, 12px); + width: 100%; + } + .description { + max-height: 64px; + overflow: hidden; + max-width: 80%; + text-overflow: ellipsis; + word-break: break-word; + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-xs, 14px); + line-height: var(--ddd-lh-150, 1.5); + } + + @media (max-width: 1200px) { + :host { + min-width: calc( + 2 * 264px + var(--ddd-spacing-6, 24px) + 2 * + var(--ddd-spacing-12, 56px) + ); + } + } + + @media (max-width: 800px) { + :host { + min-width: calc(264px + 2 * var(--ddd-spacing-12, 56px)); + } + + app-hax-site-bar { + --main-banner-width: 60vw; + } + + .description { + max-height: 24px; + font-size: var(--ddd-font-size-3xs, 8px); + font-family: var(--ddd-font-primary, sans-serif); + } + + .scroll-left, + .scroll-right { + min-width: var(--ddd-spacing-10, 40px); + padding: var(--ddd-spacing-2, 8px); + font-size: var(--ddd-font-size-s, 16px); + } + } + @media (max-width: 640px) { + app-hax-site-bar a { + font-size: var(--ddd-font-size-xs, 14px); + } + app-hax-site-bar { + --main-banner-width: 70vw; + } + + li { + width: 240px; + min-width: 240px; + } + + #results::after { + min-width: 240px; + } + /* Mobile: Show only 1 item, hide arrows */ + .scroll-left, + .scroll-right { + display: none; + } + + .carousel-container { + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-4, 16px); + justify-content: center; + } + + #results { + /* Single item takes full width on mobile */ + gap: 0; + scroll-snap-type: x mandatory; + overflow-x: auto; + scrollbar-width: none; + -ms-overflow-style: none; + } + } + span[slot="band"] { + height: var(--ddd-spacing-12, 48px); + overflow: hidden; + text-overflow: ellipsis; + margin-bottom: var(--ddd-spacing-2, 8px); + } + :host([dark]) #noResult, + body.dark-mode #noResult { + color: var(--ddd-theme-default-limestoneGray, #f5f5f5); + } + + #noResult { + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-s, 16px); + color: var(--ddd-theme-default-coalyGray, #444); + text-align: center; + padding: var(--ddd-spacing-6, 24px); + margin: var(--ddd-spacing-4, 16px); + } + + /* Screen reader only text */ + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + `, + ]; + } + // Calculate which dot indicators to display (max 10) + getVisibleDotRange() { + const maxDots = 10; + if (this.totalItems <= maxDots) { + // Show all dots if we have 10 or fewer items + return { start: 1, end: this.totalItems }; + } + + // Calculate the range to keep current index centered when possible + const halfRange = Math.floor(maxDots / 2); + let start = Math.max(1, this.currentIndex - halfRange); + let end = Math.min(this.totalItems, start + maxDots - 1); + + // Adjust start if we're near the end + if (end === this.totalItems) { + start = Math.max(1, end - maxDots + 1); + } + + return { start, end }; + } + + // Return items sorted according to the current sortOption + getSortedItems() { + const items = Array.isArray(this.displayItems) + ? [...this.displayItems] + : []; + const sortOption = this.sortOption || "az"; + + const getTitle = (item) => { + return (item.title || "").toString().toLowerCase(); + }; + + const getDate = (item, field) => { + const value = Number( + varGet( + item, + `metadata.site.${field}`, + varGet(item, "metadata.site.created", 0), + ), + ); + return Number.isNaN(value) ? 0 : value; + }; + + const getThemeName = (item) => { + const themeElement = varGet(item, "metadata.theme.element", ""); + if ( + themeElement && + store.themesData && + store.themesData[themeElement] && + store.themesData[themeElement].name + ) { + return store.themesData[themeElement].name + .toString() + .toLowerCase(); + } + return themeElement.toString().toLowerCase(); + }; + + items.sort((a, b) => { + switch (sortOption) { + case "za": + return getTitle(b).localeCompare(getTitle(a)); + case "newest": + return getDate(b, "updated") - getDate(a, "updated"); + case "oldest": + return getDate(a, "updated") - getDate(b, "updated"); + case "theme": + return getThemeName(a).localeCompare(getThemeName(b)); + case "az": + default: + return getTitle(a).localeCompare(getTitle(b)); + } + }); + + return items; + } + + render() { + // Update total items count based on sorted items + const itemsToRender = this.getSortedItems(); + this.totalItems = itemsToRender.length; + const dotRange = this.getVisibleDotRange(); + + return html` +
+ Previous + +
    \n ${itemsToRender.length > 0 + ? itemsToRender.map( + (item) => + html`
  • + + + ${item.title} + + ${item.author} + +
    + ${item.description} +
    +
    +
    +
  • `, + ) + : html`
    + No + results${this.searchTerm !== "" + ? html`"${this.searchTerm}"` + : ", Create a new site!"} +
    `} +
+ Next + + ${this.totalItems > 1 + ? html` +
+ ${Array.from( + { length: dotRange.end - dotRange.start + 1 }, + (_, index) => { + const pageNumber = dotRange.start + index; + return html` + + `; + }, + )} +
+ ` + : ""} +
+ `; + } + + scrollLeft() { + // Don't scroll if at the beginning or only one item + if (this.currentIndex <= 1 || this.totalItems <= 1) return; + + const itemWidth = 264 + 24; // item width + gap + this.shadowRoot + .querySelector("#results") + .scrollBy({ left: -itemWidth, behavior: "smooth" }); + } + + scrollRight() { + // Don't scroll if at the end or only one item + if (this.currentIndex >= this.totalItems || this.totalItems <= 1) return; + + const itemWidth = 264 + 24; // item width + gap + this.shadowRoot + .querySelector("#results") + .scrollBy({ left: itemWidth, behavior: "smooth" }); + } + + handlePointerStart(e) { + const resultsEl = this.shadowRoot.querySelector("#results"); + this.isPointerDown = true; + this.isDragging = false; + this.startX = + (e.type === "mousedown" ? e.clientX : e.touches[0].clientX) - + resultsEl.offsetLeft; + this.scrollLeftVal = resultsEl.scrollLeft; + e.preventDefault(); + } + + handlePointerMove(e) { + if (!this.isPointerDown) return; + + const resultsEl = this.shadowRoot.querySelector("#results"); + const x = + (e.type === "mousemove" ? e.clientX : e.touches[0].clientX) - + resultsEl.offsetLeft; + const walk = (x - this.startX) * 2; // Multiply for faster scrolling + + // Only start dragging if we've moved a significant distance + if (Math.abs(walk) > 5) { + this.isDragging = true; + // Disable smooth scrolling during drag for immediate feedback + resultsEl.style.scrollBehavior = "auto"; + } + + if (this.isDragging) { + resultsEl.scrollLeft = this.scrollLeftVal - walk; + e.preventDefault(); + } + } + + handlePointerEnd(e) { + this.isPointerDown = false; + + // Restore smooth scrolling behavior + const resultsEl = this.shadowRoot.querySelector("#results"); + if (resultsEl) { + resultsEl.style.scrollBehavior = "smooth"; + } + + // Use timeout to allow for click events to fire before resetting drag state + setTimeout(() => { + this.isDragging = false; + }, 100); + } + + handleScroll(e) { + if (this.totalItems <= 1) return; + + const resultsEl = e.target; + const scrollLeft = resultsEl.scrollLeft; + const scrollWidth = resultsEl.scrollWidth; + const clientWidth = resultsEl.clientWidth; + const itemWidth = 264 + 24; // item width + gap + + // Check if we're near the end (within 50px of the end) + const isNearEnd = scrollLeft + clientWidth >= scrollWidth - 50; + + if (isNearEnd) { + // If we're near the end, set to last item + this.currentIndex = this.totalItems; + } else { + // Calculate current index based on scroll position + // Use a threshold-based approach: if more than half of an item is visible, consider it active + const rawIndex = scrollLeft / itemWidth; + this.currentIndex = Math.min( + Math.max(1, Math.floor(rawIndex) + 1), + this.totalItems, + ); + } + } + + goToPage(pageNumber) { + const resultsEl = this.shadowRoot.querySelector("#results"); + const itemWidth = 264 + 24; // item width + gap + const targetScrollLeft = (pageNumber - 1) * itemWidth; + + resultsEl.scrollTo({ + left: targetScrollLeft, + behavior: "smooth", + }); + + this.currentIndex = pageNumber; + } + + getThemeImage(item) { + const themeElement = varGet(item, "metadata.theme.element", ""); + if (themeElement && store.themesData && store.themesData[themeElement]) { + let thumbnailPath = store.themesData[themeElement].thumbnail || ""; + if (thumbnailPath && thumbnailPath.startsWith("@haxtheweb/")) { + // Navigate from current file to simulate node_modules structure and resolve path + // Current file: elements/app-hax/lib/v2/app-hax-search-results.js + // Need to go up to webcomponents root, then navigate to the package + // In node_modules: @haxtheweb/package-name becomes ../../../../@haxtheweb/package-name + const packagePath = "../../../../" + thumbnailPath; + thumbnailPath = new URL(packagePath, import.meta.url).href; + } + return thumbnailPath; + } + return ""; + } + + getItemDetails(item) { + const details = { + created: varGet(item, "metadata.site.created", new Date() / 1000), + updated: varGet(item, "metadata.site.updated", new Date() / 1000), + pages: varGet(item, "metadata.pageCount", 0), + url: item.slug, + }; + return details; + } + + openedChanged(e) { + store.appEl.playSound("click"); + if (!e.detail.value) { + this.shadowRoot + .querySelector("app-hax-site-details") + .setAttribute("tabindex", "-1"); + } else { + this.shadowRoot + .querySelector("app-hax-site-details") + .removeAttribute("tabindex"); + } + } +} +customElements.define(AppHaxSearchResults.tag, AppHaxSearchResults); diff --git a/elements/app-hax/lib/v2/app-hax-simple-hat-progress.js b/elements/app-hax/lib/v2/app-hax-simple-hat-progress.js new file mode 100644 index 0000000000..78c807f935 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-simple-hat-progress.js @@ -0,0 +1,124 @@ +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; + +export class AppHaxSimpleHatProgress extends DDDSuper(LitElement) { + static get tag() { + return "app-hax-simple-hat-progress"; + } + + constructor() { + super(); + this.progress = 0; + this.max = 100; + } + + static get properties() { + return { + progress: { type: Number }, + max: { type: Number }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + display: block; + width: 160px; + height: 160px; + position: relative; + } + + .hat-container { + position: relative; + width: 100%; + height: 100%; + } + + .hat-image { + width: 100%; + height: 100%; + pointer-events: none; + z-index: 2; + position: relative; + } + + .progress-bar { + position: absolute; + bottom: 38px; + left: 8px; + right: 8px; + height: 16px; + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + border-radius: var(--ddd-radius-sm, 4px); + overflow: hidden; + z-index: 1; + } + + .progress-fill { + height: 100%; + background: linear-gradient( + 90deg, + var(--ddd-theme-default-original87Pink, #e4007c) 0%, + var(--ddd-theme-default-keystoneYellow, #ffd100) 50%, + var(--ddd-theme-default-futureLime, #99cc33) 100% + ); + border-radius: var(--ddd-radius-sm, 4px); + transition: width 0.5s ease; + width: 0%; + } + + .progress-text { + position: absolute; + top: 32%; + left: 52%; + transform: translate(-50%, -50%); + color: var(--ddd-theme-default-white, white); + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-s, 16px); + font-weight: var(--ddd-font-weight-bold, 700); + text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5); + z-index: 3; + } + `, + ]; + } + + updated(changedProperties) { + if (changedProperties.has("progress")) { + const progressFill = this.shadowRoot.querySelector(".progress-fill"); + if (progressFill) { + const percentage = Math.min( + 100, + Math.max(0, (this.progress / this.max) * 100), + ); + progressFill.style.width = `${percentage}%`; + } + } + } + + render() { + const percentage = Math.min( + 100, + Math.max(0, (this.progress / this.max) * 100), + ); + + return html` +
+ Progress Hat +
+
+
+
${Math.round(percentage)}%
+
+ `; + } +} + +customElements.define(AppHaxSimpleHatProgress.tag, AppHaxSimpleHatProgress); diff --git a/elements/app-hax/lib/v2/app-hax-site-bar.js b/elements/app-hax/lib/v2/app-hax-site-bar.js new file mode 100644 index 0000000000..e8be5eaeae --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-site-bar.js @@ -0,0 +1,584 @@ +/* eslint-disable no-console */ +// dependencies / things imported +import { html, css, unsafeCSS } from "lit"; +import "@haxtheweb/simple-icon/lib/simple-icons.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-button-lite"; +import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; +import "@haxtheweb/simple-toolbar/lib/simple-toolbar-button.js"; +import "@haxtheweb/simple-fields/lib/simple-context-menu.js"; +import { toJS } from "mobx"; +import { store } from "./AppHaxStore.js"; +import "./app-hax-user-access-modal.js"; +import "./app-hax-confirmation-modal.js"; + +const DropDownBorder = new URL( + "../assets/images/DropDownBorder.svg", + import.meta.url, +); +// EXPORT (so make available to other documents that reference this file) a class, that extends LitElement +// which has the magic life-cycles and developer experience below added +export class AppHaxSiteBars extends SimpleColors { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-site-bar"; + } + + // HTMLElement life-cycle, built in; use this for setting defaults + constructor() { + super(); + this.showOptions = false; + this.inprogress = false; + this.textInfo = {}; + this.siteId = ""; + this.description = ""; + } + + // properties that you wish to use as data in HTML, CSS, and the updated life-cycle + static get properties() { + return { + ...super.properties, + showOptions: { type: Boolean }, + inprogress: { type: Boolean, reflect: true }, + textInfo: { type: Object }, + siteId: { type: String, reflect: true, attribute: "site-id" }, + title: { type: String }, + slug: { type: String }, + description: { type: String }, + siteUrl: { type: String, attribute: "site-url" }, + image: { type: String }, + }; + } + + toggleOptionsMenu() { + const menu = this.shadowRoot.querySelector("simple-context-menu"); + const button = this.shadowRoot.querySelector("#settingsIcon"); + if (menu && button) { + menu.toggle(button); + } + } + + closeOptionsMenu() { + const menu = this.shadowRoot.querySelector("simple-context-menu"); + if (menu) { + menu.close(); + } + } + + handleKeydown(e, callback) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + callback.call(this); + } + } + + // Extract site machine name from full path + getSiteMachineName() { + if (!this.slug) return ""; + // Extract just the last part of the path (machine name) + const parts = this.slug.split("/").filter((part) => part.length > 0); + return parts.length > 0 ? parts[parts.length - 1] : this.slug; + } + copySite() { + this.closeOptionsMenu(); + this.siteOperation("copySite", "Copy", "icons:content-copy"); + } + + downloadSite() { + this.closeOptionsMenu(); + this.siteOperation("downloadSite", "Download", "file-download"); + } + + archiveSite() { + this.closeOptionsMenu(); + this.siteOperation("archiveSite", "Archive", "icons:archive"); + } + + openUserAccess() { + // Close the options menu first + this.closeOptionsMenu(); + + // Set the active site ID so the modal can access store.activeSite + store.activeSiteId = this.siteId; + + // Import simple-modal and then show the user access modal + import("@haxtheweb/simple-modal/simple-modal.js").then(() => { + // Create and show the user access modal + const modal = document.createElement("app-hax-user-access-modal"); + + // Set the site title for context + if (this.title) { + modal.siteTitle = this.title; + } + + // Show the modal using the simple-modal system + const evt = new CustomEvent("simple-modal-show", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + title: "User Access", + elements: { content: modal }, + invokedBy: this, + styles: { + "--simple-modal-titlebar-background": + "var(--ddd-theme-default-nittanyNavy, #001e44)", + "--simple-modal-titlebar-color": "light-dark(black, white)", + "--simple-modal-width": "90vw", + "--simple-modal-max-width": "var(--ddd-spacing-32, 480px)", + "--simple-modal-min-width": "300px", + "--simple-modal-z-index": "1000", + "--simple-modal-height": "auto", + "--simple-modal-min-height": "300px", + "--simple-modal-max-height": "80vh", + "--simple-modal-titlebar-height": "80px", + "--simple-modal-border-radius": "var(--ddd-radius-md, 8px)", + "--simple-modal-background": + "var(--ddd-theme-default-white, white)", + "--simple-modal-box-shadow": "var(--ddd-boxShadow-xl)", + }, + }, + }); + + this.dispatchEvent(evt); + }); + } + + // Site operation handler using new confirmation modal + siteOperation(op, opName, icon) { + if (store.appEl && store.appEl.playSound) { + store.appEl.playSound("click"); + } + + store.activeSiteOp = op; + store.activeSiteId = this.siteId; + + // Find the site data from the store + const site = toJS( + store.manifest.items.filter((item) => item.id === this.siteId).pop(), + ); + + if (!site) { + console.error("Site not found for ID:", this.siteId); + return; + } + + // Create and configure the confirmation modal + const modal = document.createElement("app-hax-confirmation-modal"); + modal.title = `${opName} ${site.metadata.site.name}?`; + modal.message = `Are you sure you want to ${op.replace("Site", "")} ${site.metadata.site.name}?`; + modal.confirmText = "Confirm"; + modal.cancelText = "Cancel"; + + // Mark archive operations as dangerous for red styling + modal.dangerous = op === "archiveSite"; + + modal.confirmAction = this.confirmOperation.bind(this); + modal.cancelAction = this.cancelOperation.bind(this); + + // Add modal to document and show it + document.body.appendChild(modal); + modal.openModal(); + + // Clean up modal when it closes + modal.addEventListener( + "close", + () => { + document.body.removeChild(modal); + }, + { once: true }, + ); + } + + cancelOperation() { + store.activeSiteOp = ""; + store.activeSiteId = null; + globalThis.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + }), + ); + if (store.appEl && store.appEl.playSound) { + store.appEl.playSound("error"); + } + } + + async confirmOperation() { + const op = toJS(store.activeSiteOp); + const site = toJS(store.activeSite); + + if (!site) { + console.error("No active site found for operation:", op); + return; + } + + // Make the API call to perform the operation + await store.AppHaxAPI.makeCall( + op, + { + site: { + name: site.metadata.site.name, + id: site.id, + }, + }, + true, + () => { + const activeOp = toJS(store.activeSiteOp); + // Download is special - it opens a download link + if (activeOp === "downloadSite") { + globalThis.open( + store.AppHaxAPI.lastResponse.downloadSite.data.link, + "_blank", + ); + } else { + // For copy and archive, refresh the site listing + store.refreshSiteListing(); + } + }, + ); + + globalThis.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + }), + ); + + if (store.appEl && store.appEl.playSound) { + store.appEl.playSound("success"); + } + + store.toast( + `${site.metadata.site.name} ${op.replace("Site", "")} successful!`, + 3000, + { + hat: "random", + }, + ); + } + + // CSS - specific to Lit + static get styles() { + return [ + super.styles, + css` + :host { + text-align: left; + width: 264px; + max-width: 264px; + font-family: var(--ddd-font-primary); + color: light-dark( + var(--ddd-theme-default-nittanyNavy), + var(--ddd-theme-default-white) + ); + background-color: light-dark( + white, + var(--ddd-theme-default-coalyGray, #222) + ); + border: var(--ddd-border-sm); + border-color: light-dark( + var(--ddd-theme-default-slateGray, #c4c4c4), + var(--ddd-theme-default-slateGray, #666) + ); + min-height: 260px; + box-shadow: light-dark( + 2px 2px 12px #1c1c1c, + 2px 2px 12px rgba(0, 0, 0, 0.3) + ); + border-radius: var(--ddd-radius-sm, 4px); + transition: all 0.2s ease; + overflow: visible; + } + :host(:hover), + :host(:focus-within) { + transform: translateY(-2px); + border-color: light-dark( + var(--ddd-theme-default-keystoneYellow, #ffd100), + var(--ddd-theme-default-keystoneYellow, #ffd100) + ); + box-shadow: light-dark( + 4px 8px 24px rgba(28, 28, 28, 0.15), + 4px 8px 24px rgba(0, 0, 0, 0.5) + ); + } + #mainCard { + display: flex; + flex-direction: column; + overflow: visible; + } + .cardContent { + padding: 12px 16px 20px; + overflow: visible; + } + .imageLink img { + width: 100%; + height: 125px; + object-fit: cover; + border-top-right-radius: 8px; + border-top-left-radius: 8px; + border-bottom: solid var(--ddd-theme-default-nittanyNavy) 8px; + overflow: clip; + justify-self: center; + } + .imageLink { + position: relative; + display: block; + width: 100%; + text-decoration: none; + cursor: pointer; + } + .settings-button { + position: relative; + display: inline-block; + align-self: center; + overflow: visible; + } + simple-context-menu { + --simple-context-menu-background: var( + --simple-toolbar-button-bg, + white + ); + --simple-context-menu-background-dark: var( + --simple-toolbar-button-bg, + #000000 + ); + --simple-context-menu-border-color: var( + --simple-toolbar-button-border-color, + var(--simple-toolbar-border-color, #e0e0e0) + ); + --simple-context-menu-border-color-dark: var( + --simple-toolbar-button-border-color, + var(--simple-toolbar-border-color, #444) + ); + --simple-context-menu-shadow: var( + --simple-toolbar-menu-list-box-shadow, + 0 2px 8px rgba(0, 0, 0, 0.15) + ); + --simple-context-menu-min-width: 180px; + } + simple-toolbar-button { + --simple-toolbar-button-border-radius: 0; + --simple-toolbar-button-hover-border-color: transparent; + cursor: pointer; + } + + .titleBar { + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--ddd-font-size-xs, 14px); + color: light-dark( + var(--ddd-theme-default-nittanyNavy), + var(--ddd-theme-default-white) + ); + overflow: visible; + position: relative; + } + .titleBar > div:first-child { + flex: 1; + min-width: 0; + overflow: hidden; + } + p { + font-size: var(--ddd-font-size-4xs, 12px); + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-2, 8px) + var(--ddd-spacing-1, 6px) var(--ddd-spacing-2, 10px); + margin: 0; + line-height: 1.4; + } + ::slotted([slot="heading"]) { + font-size: var(--ddd-font-size-xs, 14px); + font-weight: var(--ddd-font-weight-bold, 700); + color: light-dark( + var(--ddd-theme-default-nittanyNavy), + var(--ddd-theme-default-white) + ); + text-decoration: none; + display: block; + margin: 0; + line-height: 1.2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; + cursor: pointer; + } + ::slotted([slot="heading"]:hover) { + text-decoration: underline; + } + .site-slug { + font-size: var(--ddd-font-size-4xs, 10px); + color: light-dark( + var(--ddd-theme-default-slateGray, #666), + var(--ddd-theme-default-limestoneGray, #aaa) + ); + font-family: var(--ddd-font-navigation, monospace); + margin: var(--ddd-spacing-1, 4px) 0 0 0; + display: block; + line-height: 1; + } + button { + display: flex; + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + border: var(--ddd-border-xs, 1px solid) transparent; + border-radius: var(--ddd-radius-sm, 4px); + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-3xs, 11px); + font-weight: var(--ddd-font-weight-medium, 500); + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + min-height: var(--ddd-spacing-7, 28px); + align-items: center; + justify-content: start; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: var(--ddd-boxShadow-sm); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + button:hover { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + transform: translateY(-1px); + box-shadow: var(--ddd-boxShadow-md); + } + .cardBottom { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 6px; + padding: 0px 12px 16px 12px; + gap: 4px; + } + .cardBottom button { + flex: 1; + margin: 0 2px; + min-width: 0; + } + ::slotted(a[slot="heading"]), + ::slotted(span[slot="subHeading"]), + ::slotted(div[slot="pre"]) { + display: block; + } + `, + ]; + } + + __clickButton() { + this.opened = !this.opened; + } + + // HTML - specific to Lit + render() { + return html` +
+ ${this.image + ? html` + + Screenshot of ${this.title || + + ` + : ""} + +
+
+
+ + ${this.slug + ? html`
+ ${this.getSiteMachineName()} +
` + : ""} +
+
+ + + Options + + + + + + ${store.appSettings && store.appSettings.haxiamAddUserAccess + ? html` + + ` + : ""} + +
+
+ + + +
+ + + +
+
+
+ `; + } + + // HAX specific callback + // This teaches HAX how to edit and work with your web component + /** + * haxProperties integration via file reference + */ +} +customElements.define(AppHaxSiteBars.tag, AppHaxSiteBars); diff --git a/elements/app-hax/lib/v2/app-hax-site-button.js b/elements/app-hax/lib/v2/app-hax-site-button.js new file mode 100644 index 0000000000..cfbf05cb91 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-site-button.js @@ -0,0 +1,159 @@ +/* eslint-disable no-console */ +// dependencies / things imported +import { html, css } from "lit"; +import "@haxtheweb/simple-icon/lib/simple-icons.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import "wired-elements/lib/wired-button.js"; + +const postIt = new URL("../assets/images/PostIt.svg", import.meta.url).href; + +// EXPORT (so make available to other documents that reference this file) a class, that extends LitElement +// which has the magic life-cycles and developer experience below added +export class AppHaxSiteButton extends SimpleColors { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-site-button"; + } + + // HTMLElement life-cycle, built in; use this for setting defaults + constructor() { + super(); + this.label = null; + this.value = null; + this.disabled = false; + this.elevation = "3"; + this.active = false; + this.comingSoon = false; + this.addEventListener("click", this._handleClick); + this.addEventListener("focus", this._handleFocus); + this.addEventListener("blur", this._handleBlur); + this.addEventListener("mouseover", this._handleFocus); + this.addEventListener("mouseout", this._handleBlur); + } + + // properties that you wish to use as data in HTML, CSS, and the updated life-cycle + static get properties() { + return { + label: { type: String }, + value: { type: String }, + disabled: { type: Boolean, reflect: true }, + elevation: { type: Number }, + active: { type: Boolean, reflect: true }, + comingSoon: { type: Boolean, reflect: true, attribute: "coming-soon" }, + }; + } + + // CSS - specific to Lit + static get styles() { + return css` + :host { + --background-color: transparent; + --background-color-active: white; + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: fit-content; + margin: 20px 0; + } + :host([coming-soon]) .haxButton { + pointer-events: none; + background-color: var(--simple-colors-default-theme-grey-6); + } + @media (max-width: 800px) { + :host { + width: 320px; + } + } + :host([active]) .haxButton { + color: var(--app-hax-background-color, var(--background-color-active)); + background-color: var(--app-hax-accent-color, var(--accent-color)); + } + .haxButton { + background-color: var( + --app-hax-background-color, + var(--background-color) + ); + color: var(--app-hax-accent-color, var(--accent-color)); + font-size: var(--app-hax-site-button-font-size, 26px); + } + .contents { + display: flex; + justify-content: right; + } + .label { + width: var(--app-hax-site-button-width, auto); + min-width: var(--app-hax-site-button-min-width, auto); + height: var(--app-hax-site-button-height, auto); + display: inline-flex; + } + .coming-soon { + display: block; + height: 90px; + width: 110px; + z-index: 1; + position: absolute; + margin-right: -25px; + margin-top: -25px; + } + `; + } + + _handleFocus() { + if (!this.disabled && !this.comingSoon) { + this.active = true; + this.elevation = "5"; + } + } + + _handleBlur() { + if (!this.disabled && !this.comingSoon) { + this.active = false; + this.elevation = "3"; + } + } + + _handleClick() { + if (!this.disabled && !this.comingSoon) { + this.shadowRoot.querySelector(".haxButton").blur(); + } + } + + // HTML - specific to Lit + render() { + return html` + +
+ ${this.label} + ${this.comingSoon + ? html`` + : ``} +
+
+ `; + } + + // HAX specific callback + // This teaches HAX how to edit and work with your web component + /** + * haxProperties integration via file reference + */ +} + +customElements.define(AppHaxSiteButton.tag, AppHaxSiteButton); diff --git a/elements/app-hax/lib/v2/app-hax-site-creation-modal.js b/elements/app-hax/lib/v2/app-hax-site-creation-modal.js new file mode 100644 index 0000000000..53438c8756 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-site-creation-modal.js @@ -0,0 +1,1026 @@ +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import "web-dialog/index.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import "@haxtheweb/simple-icon/lib/simple-icons.js"; +import "@haxtheweb/promise-progress/promise-progress.js"; +import "./app-hax-simple-hat-progress.js"; +import { store } from "./AppHaxStore.js"; +import { toJS } from "mobx"; + +export class AppHaxSiteCreationModal extends DDDSuper(LitElement) { + static get tag() { + return "app-hax-site-creation-modal"; + } + + constructor() { + super(); + this.open = false; + this.title = ""; + this.description = ""; + this.source = ""; + this.template = ""; + this.themeElement = ""; + this.siteName = ""; + this.currentStep = 1; // 1: naming, 2: creating, 3: success + this.isCreating = false; + this.creationProgress = 0; + this.errorMessage = ""; + this.showConfetti = false; + this.siteUrl = ""; + this.creationCancelled = false; + this.promises = []; + this.max = 100; + this.skeletonData = null; + } + + static get properties() { + return { + open: { type: Boolean, reflect: true }, + title: { type: String }, + description: { type: String }, + source: { type: String }, + template: { type: String }, + themeElement: { type: String }, + siteName: { type: String }, + currentStep: { type: Number }, + isCreating: { type: Boolean }, + creationProgress: { type: Number }, + errorMessage: { type: String }, + showConfetti: { type: Boolean }, + siteUrl: { type: String }, + creationCancelled: { type: Boolean }, + promises: { type: Array }, + max: { type: Number }, + skeletonData: { type: Object }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + font-family: var(--ddd-font-primary, sans-serif); + } + + web-dialog { + --dialog-width: var(--ddd-spacing-32, 480px); + --dialog-max-width: 90vw; + --dialog-max-height: 80vh; + --dialog-border-radius: var(--ddd-radius-md, 8px); + z-index: 1000; + } + + web-dialog::part(backdrop) { + background: rgba(0, 0, 0, 0.6); + } + + web-dialog::part(dialog) { + background: var(--ddd-theme-default-white, white); + border-radius: var(--ddd-radius-md, 8px); + box-shadow: var(--ddd-boxShadow-xl); + padding: 0; + overflow: hidden; + } + + .modal-header { + padding: var(--ddd-spacing-4, 16px); + border-bottom: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-limestoneGray, #f5f5f5); + display: flex; + align-items: center; + justify-content: space-between; + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + } + + .modal-title { + font-size: var(--ddd-font-size-m, 18px); + font-weight: var(--ddd-font-weight-bold, 700); + margin: 0; + } + + .close-button { + background: transparent; + border: none; + color: var(--ddd-theme-default-white, white); + cursor: pointer; + padding: var(--ddd-spacing-2, 8px); + border-radius: var(--ddd-radius-sm, 4px); + transition: background-color 0.2s ease; + } + + .close-button:hover { + background: rgba(255, 255, 255, 0.1); + } + + .modal-content { + min-height: var(--ddd-spacing-20, 200px); + display: flex; + flex-direction: column; + align-items: center; + position: relative; + padding: var(--ddd-spacing-6, 24px); + } + + .step-indicator { + display: flex; + align-items: center; + justify-content: center; + gap: var(--ddd-spacing-2, 8px); + margin-bottom: var(--ddd-spacing-4, 16px); + } + + .step-dot { + width: var(--ddd-spacing-3, 12px); + height: var(--ddd-spacing-3, 12px); + border-radius: 50%; + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + transition: background-color 0.3s ease; + } + + .step-dot.active { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + } + + .step-dot.completed { + background: var(--ddd-theme-default-futureLime, #99cc33); + } + + .template-info { + display: flex; + gap: var(--ddd-spacing-4, 16px); + margin-bottom: var(--ddd-spacing-4, 16px); + padding: var(--ddd-spacing-3, 12px); + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + border-radius: var(--ddd-radius-sm, 4px); + width: 100%; + align-items: flex-start; + } + + .template-image { + flex-shrink: 0; + width: 120px; + height: 80px; + border-radius: var(--ddd-radius-sm, 4px); + overflow: hidden; + border: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-slateGray, #666); + } + + .template-image img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } + + .template-details { + flex: 1; + min-width: 0; + text-align: left; + } + + .template-title { + font-size: var(--ddd-font-size-s, 16px); + font-weight: var(--ddd-font-weight-bold, 700); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + margin: 0 0 var(--ddd-spacing-2, 8px) 0; + } + + .template-description { + font-size: var(--ddd-font-size-xs, 14px); + color: var(--ddd-theme-default-coalyGray, #444); + margin: 0; + } + + .form-group { + width: 100%; + margin-bottom: var(--ddd-spacing-4, 16px); + position: relative; + } + + .form-label { + display: block; + font-size: var(--ddd-font-size-xs, 14px); + font-weight: var(--ddd-font-weight-medium, 500); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + margin-bottom: var(--ddd-spacing-2, 8px); + text-align: left; + } + + .form-input { + width: 100%; + padding: var(--ddd-spacing-3, 12px) var(--ddd-spacing-3, 12px) + var(--ddd-spacing-3, 12px) var(--ddd-spacing-8, 32px); + border: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-slateGray, #666); + border-style: solid; + border-width: var(--ddd-border-sm, 2px); + border-radius: var(--ddd-radius-sm, 4px); + border-color: var(--ddd-theme-default-slateGray, #666); + font-size: var(--ddd-font-size-s, 16px); + font-family: var(--ddd-font-primary, sans-serif); + box-sizing: border-box; + transition: all 0.2s ease; + background-color: var(--ddd-theme-default-limestoneMaxLight, #f5f5f5); + color: var(--ddd-theme-default-coalyGray, #222); + min-height: var(--ddd-spacing-8, 32px); + } + + .form-icon { + position: absolute; + left: var(--ddd-spacing-2, 8px); + bottom: var(--ddd-spacing-4, 8px); + font-size: var(--ddd-font-size-xs, 14px); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + pointer-events: none; + z-index: 1; + --simple-icon-width: var(--ddd-icon-3xs, 20px); + --simple-icon-height: var(--ddd-icon-3xs, 20px); + } + .form-input:focus { + outline: none; + border: var(--ddd-border-md, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + } + + .form-input:invalid { + border-color: var(--ddd-theme-default-original87Pink, #e4007c); + } + + .form-input:required:valid { + border-color: var(--ddd-theme-default-futureLime, #99cc33); + } + + .error-message { + color: var(--ddd-theme-default-original87Pink, #e4007c); + font-size: var(--ddd-font-size-xs, 12px); + margin-top: var(--ddd-spacing-2, 8px); + text-align: left; + } + + .progress-container { + width: 100%; + margin: var(--ddd-spacing-4, 16px) 0; + } + + .progress-text { + font-size: var(--ddd-font-size-s, 16px); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + margin-bottom: var(--ddd-spacing-4, 16px); + } + + .hat-progress-container { + display: flex; + justify-content: center; + align-items: center; + margin: var(--ddd-spacing-4, 16px) 0; + padding: 0 var(--ddd-spacing-4, 16px); + min-height: 180px; + } + + app-hax-simple-hat-progress { + width: 160px; + height: 160px; + max-width: calc(100% - var(--ddd-spacing-8, 32px)); + } + + .progress-bar { + width: 100%; + height: var(--ddd-spacing-3, 12px); + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + border-radius: var(--ddd-radius-sm, 4px); + overflow: hidden; + margin-bottom: var(--ddd-spacing-2, 8px); + } + + .progress-fill { + height: 100%; + background: linear-gradient( + 90deg, + var(--ddd-theme-default-nittanyNavy, #001e44) 0%, + var(--ddd-theme-default-futureLime, #99cc33) 100% + ); + border-radius: var(--ddd-radius-sm, 4px); + transition: width 0.5s ease; + width: 0%; + } + + .progress-percentage { + font-size: var(--ddd-font-size-xs, 12px); + color: var(--ddd-theme-default-coalyGray, #444); + } + + promise-progress { + display: none; + } + + .success-content { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + } + + .success-hat-container { + display: flex; + justify-content: center; + align-items: center; + margin: var(--ddd-spacing-2, 8px) 0; + } + + .success-icon { + font-size: var(--ddd-font-size-4xl, 48px); + color: var(--ddd-theme-default-futureLime, #99cc33); + margin-bottom: var(--ddd-spacing-4, 16px); + } + + .success-title { + font-size: var(--ddd-font-size-s, 16px); + font-weight: var(--ddd-font-weight-medium, 500); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + margin: var(--ddd-spacing-2, 8px) 0 var(--ddd-spacing-1, 4px) 0; + } + + .success-subtitle { + font-size: var(--ddd-font-size-xs, 12px); + color: var(--ddd-theme-default-coalyGray, #444); + margin: 0 0 var(--ddd-spacing-2, 8px) 0; + line-height: var(--ddd-lh-140, 1.4); + } + + .button-group { + display: flex; + gap: var(--ddd-spacing-3, 12px); + justify-content: center; + margin-top: var(--ddd-spacing-4, 16px); + width: 100%; + } + + .button { + padding: var(--ddd-spacing-3, 12px) var(--ddd-spacing-4, 16px); + border-radius: var(--ddd-radius-sm, 4px); + font-size: var(--ddd-font-size-xs, 14px); + font-weight: var(--ddd-font-weight-medium, 500); + font-family: var(--ddd-font-primary, sans-serif); + cursor: pointer; + transition: all 0.2s ease; + border: none; + display: flex; + align-items: center; + justify-content: center; + gap: var(--ddd-spacing-2, 8px); + } + + .button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .button-primary { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + } + + .button-primary:hover:not(:disabled), + .button-primary:focus:not(:disabled) { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + outline: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + outline-offset: var(--ddd-spacing-1, 2px); + } + + .button-secondary { + background: transparent; + color: var(--ddd-theme-default-nittanyNavy, #001e44); + border: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-nittanyNavy, #001e44); + } + + .button-secondary:hover:not(:disabled), + .button-secondary:focus:not(:disabled) { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + outline: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-nittanyNavy, #001e44); + outline-offset: var(--ddd-spacing-1, 2px); + } + + .button-success { + background: var(--ddd-theme-default-futureLime, #99cc33); + color: var(--ddd-theme-default-white, white); + } + + .button-success:hover:not(:disabled), + .button-success:focus:not(:disabled) { + background: var(--ddd-theme-default-original87Pink, #e4007c); + outline: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-original87Pink, #e4007c); + outline-offset: var(--ddd-spacing-1, 2px); + } + + .confetti { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + overflow: hidden; + } + + .confetti-piece { + position: absolute; + width: 10px; + height: 10px; + background: var(--ddd-theme-default-futureLime, #99cc33); + animation: confetti-fall 3s linear infinite; + } + + .confetti-piece:nth-child(2n) { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + animation-delay: -0.5s; + } + + .confetti-piece:nth-child(3n) { + background: var(--ddd-theme-default-original87Pink, #e4007c); + animation-delay: -1s; + } + + .confetti-piece:nth-child(4n) { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + animation-delay: -1.5s; + } + + @keyframes confetti-fall { + 0% { + transform: translateY(-100vh) rotate(0deg); + opacity: 1; + } + 100% { + transform: translateY(100vh) rotate(360deg); + opacity: 0; + } + } + + @media (max-width: 600px) { + web-dialog { + --dialog-width: 95vw; + --dialog-max-height: 90vh; + } + + .modal-content { + padding: var(--ddd-spacing-4, 16px); + } + + .button-group { + flex-direction: column; + gap: var(--ddd-spacing-2, 8px); + margin-top: var(--ddd-spacing-3, 12px); + } + + .button { + width: 100%; + min-height: var(--ddd-spacing-10, 40px); + font-size: var(--ddd-font-size-xs, 14px); + padding: var(--ddd-spacing-3, 12px); + } + + app-hax-simple-hat-progress { + width: 120px; + height: 120px; + } + + .hat-progress-container { + padding: 0 var(--ddd-spacing-2, 8px); + min-height: 140px; + } + + .modal-title { + font-size: var(--ddd-font-size-s, 16px); + } + + .form-input { + min-height: var(--ddd-spacing-10, 40px); + font-size: var(--ddd-font-size-s, 16px); + } + } + `, + ]; + } + + openModal() { + this.open = true; + this.currentStep = 1; + // Preserve any prepopulated siteName from the caller; default to empty string + this.siteName = this.siteName || ""; + this.errorMessage = ""; + this.showConfetti = false; + this.isCreating = false; + this.creationProgress = 0; + this.creationCancelled = false; + this.siteUrl = ""; + + // Prevent body scrolling while modal is open + document.body.style.overflow = "hidden"; + + // Wait for the component to update before setting modal state + this.updateComplete.then(() => { + const modal = + this.shadowRoot && this.shadowRoot.querySelector("web-dialog"); + if (modal) { + modal.open = true; + } + + // Focus the input after the modal opens + setTimeout(() => { + const input = + this.shadowRoot && this.shadowRoot.querySelector(".form-input"); + if (input) { + input.focus(); + // Select the full value so it's easy to overwrite via keyboard + if (typeof input.select === "function") { + input.select(); + } + } + }, 100); + }); + } + + closeModal() { + // If creation is in progress, cancel it + if (this.isCreating) { + this.creationCancelled = true; + } + + // Consider it cancelled if we're in step 1 OR if we're in step 3 (success) and user chooses to stay + const wasCancelled = this.currentStep === 1 || this.currentStep === 3; + + // Removed sound effects for modal close/cancel as requested + + // Restore body scrolling + document.body.style.overflow = ""; + document.documentElement.style.overflow = ""; + + this.open = false; + const modal = + this.shadowRoot && this.shadowRoot.querySelector("web-dialog"); + if (modal) { + modal.open = false; + } + + this.currentStep = 1; + this.siteName = ""; + this.errorMessage = ""; + this.showConfetti = false; + this.isCreating = false; + this.creationProgress = 0; + this.creationCancelled = false; + this.siteUrl = ""; + this.themeElement = ""; + this.skeletonData = null; + + this.dispatchEvent( + new CustomEvent("modal-closed", { + bubbles: true, + composed: true, + detail: { cancelled: wasCancelled }, + }), + ); + } + + handleModalClosed(e) { + // web-dialog sends close event, we need to sync our state + if (this.isCreating) { + this.creationCancelled = true; + } + + // Consider it cancelled if we're in step 1 OR if we're in step 3 (success) and user closes/stays + const wasCancelled = this.currentStep === 1 || this.currentStep === 3; + + // Removed sound effects for modal close/cancel as requested + + // Restore body scrolling + document.body.style.overflow = ""; + document.documentElement.style.overflow = ""; + + this.open = false; + this.currentStep = 1; + this.siteName = ""; + this.errorMessage = ""; + this.showConfetti = false; + this.isCreating = false; + this.creationProgress = 0; + this.creationCancelled = false; + this.siteUrl = ""; + this.themeElement = ""; + this.skeletonData = null; + + this.dispatchEvent( + new CustomEvent("modal-closed", { + bubbles: true, + composed: true, + detail: { cancelled: wasCancelled }, + }), + ); + } + + handleKeyDown(e) { + if (e.key === "Enter" && this.currentStep === 1) { + this.createSite(); + } + } + + validateSiteName() { + const name = this.siteName.trim(); + if (!name) { + this.errorMessage = "Site name is required"; + return false; + } + if (name.length < 3) { + this.errorMessage = "Site name must be at least 3 characters"; + return false; + } + if (name.length > 50) { + this.errorMessage = "Site name must be less than 50 characters"; + return false; + } + if (!/^[a-zA-Z0-9\s\-_]+$/.test(name)) { + this.errorMessage = + "Site name can only contain letters, numbers, spaces, hyphens, and underscores"; + return false; + } + this.errorMessage = ""; + return true; + } + + async createSite() { + if (!this.validateSiteName()) { + return; + } + + // Set up the site data in store for the API call + store.site.name = this.siteName; + // If skeleton data exists, use its build configuration + if (this.skeletonData && this.skeletonData.build) { + store.site.structure = + this.skeletonData.build.structure || "from-skeleton"; + store.site.type = this.skeletonData.build.type || "skeleton"; + // Pass skeleton items and files to store for API formatting + if (this.skeletonData.build.items) { + store.items = this.skeletonData.build.items; + } + if (this.skeletonData.build.files) { + store.itemFiles = this.skeletonData.build.files; + } + } else { + store.site.structure = this.themeElement || "website"; + store.site.type = "own"; + store.items = null; + store.itemFiles = null; + } + store.site.theme = this.themeElement || "polaris-flex-theme"; // Use selected theme + + this.currentStep = 2; + this.isCreating = true; + this.creationProgress = 0; + this.creationCancelled = false; + + // Set up promises from store for real site creation + this.promises = toJS(store.newSitePromiseList); + + try { + // Start the promise progress system + await this.updateComplete; // Wait for render + const promiseProgress = + this.shadowRoot.querySelector("#promise-progress"); + if (promiseProgress) { + promiseProgress.process(); + } + } catch (error) { + if (!this.creationCancelled) { + this.errorMessage = "Failed to create site. Please try again."; + this.currentStep = 1; + this.isCreating = false; + console.error("Site creation error:", error); + } + } + } + + generateConfetti() { + const confettiContainer = this.shadowRoot.querySelector(".confetti"); + if (!confettiContainer) return; + + // Clear existing confetti + confettiContainer.innerHTML = ""; + + // Generate confetti pieces + for (let i = 0; i < 50; i++) { + const piece = document.createElement("div"); + piece.className = "confetti-piece"; + piece.style.left = Math.random() * 100 + "%"; + piece.style.animationDuration = Math.random() * 3 + 2 + "s"; + piece.style.animationDelay = Math.random() * 2 + "s"; + confettiContainer.appendChild(piece); + } + + // Remove confetti after animation + setTimeout(() => { + confettiContainer.innerHTML = ""; + this.showConfetti = false; + }, 5000); + } + + progressValueChanged(e) { + this.creationProgress = e.detail.value; + // Update the hat progress component + const hatProgress = this.shadowRoot.querySelector( + "app-hax-simple-hat-progress", + ); + if (hatProgress) { + hatProgress.progress = this.creationProgress; + hatProgress.requestUpdate(); + } + } + + progressMaxChanged(e) { + this.max = e.detail.value; + } + + promiseProgressFinished(e) { + if (e.detail.value) { + // Site creation completed successfully! + const createResponse = store.AppHaxAPI.lastResponse.createSite.data; + + // Set the real site URL from the API response + if (createResponse && createResponse.slug) { + this.siteUrl = createResponse.slug.replace("index.html", ""); + } else { + // Fallback URL if API doesn't return proper response + const siteSlug = this.siteName + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-"); + this.siteUrl = siteSlug; + } + + // Success! + this.currentStep = 3; + this.isCreating = false; + this.creationProgress = this.max || 100; // Ensure 100% completion + + // Update hat progress to show 100% completion + const hatProgress = this.shadowRoot.querySelector( + "app-hax-simple-hat-progress", + ); + if (hatProgress) { + hatProgress.progress = this.creationProgress; + hatProgress.requestUpdate(); + } + + this.showConfetti = true; + this.generateConfetti(); + + // After success UI renders, move focus to the primary action (Go to Site) + this.updateComplete.then(() => { + const goToSiteButton = + this.shadowRoot && + this.shadowRoot.querySelector(".button.button-success"); + if (goToSiteButton) { + goToSiteButton.focus(); + } + }); + + // Trigger confetti on main page + this.triggerMainPageConfetti(); + + // Play success sound if available + if (store.appEl && store.appEl.playSound) { + store.appEl.playSound("success"); + } + } + } + + triggerMainPageConfetti() { + // Find the main page confetti container and trigger confetti + const mainConfettiContainer = + store.appEl && + store.appEl.shadowRoot && + store.appEl.shadowRoot.querySelector("#confetti"); + if (mainConfettiContainer) { + // Import and trigger confetti on main page + import("@haxtheweb/multiple-choice/lib/confetti-container.js").then( + () => { + setTimeout(() => { + mainConfettiContainer.setAttribute("popped", ""); + // Remove the attribute after animation to allow future confetti + setTimeout(() => { + mainConfettiContainer.removeAttribute("popped"); + }, 3000); + }, 0); + }, + ); + } + } + + goToSite() { + if (this.siteUrl) { + globalThis.open(this.siteUrl, "_blank"); + } + // Restore body scrolling before closing + document.body.style.overflow = ""; + document.documentElement.style.overflow = ""; + this.closeModal(); + } + + renderStepIndicator() { + return html` +
+ ${[1, 2].map( + (step) => html` +
+ `, + )} +
+ `; + } + + renderNamingStep() { + return html` +
+ ${this.source + ? html` +
+ ${this.title} preview +
+ ` + : ""} +
+

${this.title}

+

${this.description}

+
+
+ +
+ + + + ${this.errorMessage + ? html`
${this.errorMessage}
` + : ""} +
+ `; + } + + renderNamingButtons() { + return html` +
+ + +
+ `; + } + + renderCreatingStep() { + return html` +
+

Creating your site...

+ +
+ +
+ + + +
+
+
+
+ ${this.creationProgress}% complete +
+
+ `; + } + + renderSuccessStep() { + return html` +
+
+ +
+

Site Created Successfully!

+

+ Your new site "${this.siteName}" is ready to use. +

+
+ `; + } + + renderSuccessButtons() { + return html` +
+ + +
+ `; + } + + render() { + return html` + +
+

+ ${this.currentStep === 1 + ? "Create New Site" + : this.currentStep === 2 + ? "Creating Site..." + : "Site Created!"} +

+ +
+ +
+ ${this.showConfetti ? html`
` : ""} + ${this.renderStepIndicator()} + ${this.currentStep === 1 + ? this.renderNamingStep() + : this.currentStep === 2 + ? this.renderCreatingStep() + : this.renderSuccessStep()} + ${this.currentStep === 1 + ? this.renderNamingButtons() + : this.currentStep === 3 + ? this.renderSuccessButtons() + : html``} +
+
+ `; + } +} + +customElements.define(AppHaxSiteCreationModal.tag, AppHaxSiteCreationModal); diff --git a/elements/app-hax/lib/v2/app-hax-site-details.js b/elements/app-hax/lib/v2/app-hax-site-details.js new file mode 100644 index 0000000000..ca6b8f4d82 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-site-details.js @@ -0,0 +1,379 @@ +// dependencies / things imported +import { html, css } from "lit"; +import "@haxtheweb/simple-datetime/simple-datetime.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-button-lite.js"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; +import { toJS } from "mobx"; +import { store } from "./AppHaxStore.js"; +import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import "./app-hax-confirmation-modal.js"; + +// wrapper to simplify the slug if it has additional values on it +function makeSlug(url) { + let slug = "site"; + if (url) { + let tmp = url.split("sites/"); + if (tmp.length > 1) { + slug = tmp.pop().replace("/", ""); + } + } + return slug; +} +// EXPORT (so make available to other documents that reference this file) a class, that extends LitElement +// which has the magic life-cycles and developer experience below added +export class AppHaxSiteDetails extends SimpleColors { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-site-details"; + } + + // HTMLElement life-cycle, built in; use this for setting defaults + constructor() { + super(); + this.need = "all need to succeed"; + this.details = {}; + this.siteId = ""; + this.detailOps = [ + { + name: "Copy", + op: "copySite", + icon: "icons:content-copy", + }, + { + name: "Download", + op: "downloadSite", + icon: "file-download", + }, + { + name: "Archive", + op: "archiveSite", + icon: "icons:archive", + }, + ]; + if (globalThis.HAXCMSContext && globalThis.HAXCMSContext === "php") { + this.detailOps.push({ + name: "Git", + op: "gitList", + icon: "hax:git", + }); + } + } + + // properties that you wish to use as data in HTML, CSS, and the updated life-cycle + static get properties() { + return { + ...super.properties, + details: { type: Object }, + siteId: { type: String, attribute: "site-id" }, + }; + } + + // CSS - specific to Lit + static get styles() { + return [ + super.styles, + css` + :host { + display: flex; + flex-direction: column; + justify-content: center; + font-size: 12px; + align-items: stretch; + background-color: var(--simple-colors-default-theme-light-blue-11); + color: var(--simple-colors-default-theme-light-blue-1); + max-width: 220px; + height: 208px; + border-radius: 8px; + } + + .flex-container { + flex: 1; + background-color: var(--simple-colors-default-theme-light-blue-11); + margin: 8px; + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: center; + } + .info-group { + height: 100%; + max-width: 25%; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + padding: 0px; + flex: 1; + } + simple-icon-button-lite:active, + simple-icon-button-lite:hover, + simple-icon-button-lite:focus { + background-color: var( + --simple-colors-default-theme-light-blue-8, + #cde8ff + ); + outline: 2px solid var(--simple-colors-default-theme-light-blue-1); + outline-offset: 1px; + } + + .info-headings { + font-size: 12px; + } + .info-item { + font-family: sans-serif; + display: block; + text-overflow: ellipsis; + overflow: hidden; + color: var(--simple-colors-default-theme-light-blue-1); + line-height: 12px; + max-width: 100%; + font-size: 14px; + } + .pre ::slotted(*) { + padding: 12px; + overflow: hidden; + text-overflow: ellipsis; + max-width: 50%; + display: inline-flex; + } + a { + text-decoration: underline; + } + .info-date { + color: var(--simple-colors-default-theme-light-blue-1); + line-height: 12px; + font-size: 12px; + } + + .info-icon { + --simple-icon-width: 49px; + --simple-icon-height: 49px; + --simple-icon-button-border-radius: 0px; + --simple-icon-button-border: 0px; + outline: 0; + border: 2px solid var(--simple-colors-default-theme-light-blue-1); + border-radius: 4px; + padding: 4px; + width: 80%; + } + .info-icon::part(button) { + outline: none; + } + @media (max-width: 640px) { + :host { + height: 140px; + } + .btn-group button { + padding: 4px; + margin: 4px 0; + } + .flex-container > div { + margin: 0px; + } + .info-headings { + font-size: 8px; + } + .info-date { + font-size: 8px; + line-height: 10px; + } + .info-icon { + --simple-icon-width: 30px; + --simple-icon-height: 30px; + padding: 2px; + border-radius: none; + } + .info-item { + font-size: 8px; + } + .flex-container { + margin: 2px; + } + .pre ::slotted(*) { + padding: 0px; + margin-top: 8px; + } + .info-group { + height: 24px; + } + } + `, + ]; + } + + // eslint-disable-next-line class-methods-use-this + siteOperation(e) { + // let elements; + store.appEl.playSound("click"); + var target = e.target; + // avoid label trigger + if (target.tagName === "DIV") { + target = target.parentNode; + } + const op = target.getAttribute("data-site-operation"); + const opName = target.getAttribute("data-site-operation-name"); + const siteID = target.getAttribute("data-site"); + store.activeSiteOp = op; + store.activeSiteId = siteID; + + const site = toJS( + store.manifest.items.filter((item) => item.id === siteID).pop(), + ); + + // gitlist opens in a new window + if (op === "gitList") { + // php library is basis for this button, rare instance + if (globalThis.HAXCMSContext === "php") { + // open link in new window + globalThis.open( + `gitlist/${site.metadata.site.name}`, + "_blank", + "noopener noreferrer", + ); + } + } else { + // Create and configure the confirmation modal + const modal = document.createElement("app-hax-confirmation-modal"); + modal.title = `${opName} ${site.metadata.site.name}?`; + modal.message = `Are you sure you want to ${op.replace("Site", "")} ${site.metadata.site.name}?`; + modal.confirmText = "Confirm"; + modal.cancelText = "Cancel"; + + // Mark archive operations as dangerous for red styling + modal.dangerous = op === "archiveSite"; + + modal.confirmAction = this.confirmOperation.bind(this); + modal.cancelAction = this.cancelOperation.bind(this); + + // Add modal to document and show it + document.body.appendChild(modal); + modal.openModal(); + + // Clean up modal when it closes + modal.addEventListener( + "close", + () => { + document.body.removeChild(modal); + }, + { once: true }, + ); + } + } + + cancelOperation() { + store.activeSiteOp = ""; + store.activeSiteId = null; + globalThis.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + }), + ); + store.appEl.playSound("error"); + } + + async confirmOperation() { + const op = toJS(store.activeSiteOp); + const site = toJS(store.activeSite); + // @todo bother to implement these / translate to the path via switch + await store.AppHaxAPI.makeCall( + op, + { + site: { + name: site.metadata.site.name, + id: site.id, + }, + }, + true, + () => { + const activeOp = toJS(store.activeSiteOp); + // download is weird relative to the others + if (activeOp === "downloadSite") { + // cheat to download a file path + globalThis.open( + store.AppHaxAPI.lastResponse.downloadSite.data.link, + "_blank", + ); + } else { + store.refreshSiteListing(); + } + }, + ); + globalThis.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + }), + ); + store.appEl.playSound("success"); + store.toast( + `${site.metadata.site.name} ${op.replace("Site", "")} successful!`, + 3000, + { + hat: "random", + }, + ); + } + + // HTML - specific to Lit + render() { + return html` +
+
+
+
created
+ +
+
+
updated
+ +
+
+
pages
+
${this.details.pages}
+
+
+
URL
+ ${makeSlug(this.details.url)} +
+
+
+ ${this.detailOps.map( + (item) => html` +
+ +
${item.name.toLowerCase()}
+
+ ${item.op != "gitList" ? "" : "View"} ${item.name} + ${item.op != "gitList" ? "Site" : "source"} +
+ `, + )} +
+
+ `; + } +} +customElements.define(AppHaxSiteDetails.tag, AppHaxSiteDetails); diff --git a/elements/app-hax/lib/v2/app-hax-site-login.js b/elements/app-hax/lib/v2/app-hax-site-login.js new file mode 100644 index 0000000000..b91defb6b8 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-site-login.js @@ -0,0 +1,370 @@ +import { LitElement, html, css } from "lit"; +import "@haxtheweb/simple-icon/lib/simple-icons.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import "@haxtheweb/rpg-character/rpg-character.js"; +import { store } from "./AppHaxStore.js"; +export class AppHaxSiteLogin extends DDDSuper(LitElement) { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-site-login"; + } + + // HTMLElement life-cycle, built in; use this for setting defaults + constructor() { + super(); + this.windowControllers = new AbortController(); + this.username = ""; + this.password = ""; + this.errorMSG = "Enter User name"; + this.hidePassword = true; + this.hasPass = false; + } + + // properties that you wish to use as data in HTML, CSS, and the updated life-cycle + static get properties() { + return { + ...super.properties, + username: { type: String }, + password: { type: String }, + errorMSG: { type: String }, + hidePassword: { type: Boolean }, + hasPass: { type: Boolean }, + }; + } + + firstUpdated() { + super.firstUpdated(); + setTimeout(() => { + this.shadowRoot.querySelector("input").focus(); + }, 0); + } + + // updated fires every time a property defined above changes + // this allows you to react to variables changing and use javascript to perform logic + // updated(changedProperties) { + // changedProperties.forEach((oldValue, propName) => { + // }); + // } + + // CSS - specific to Lit + static get styles() { + return [ + super.styles, + css` + :host { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding: var(--ddd-spacing-6, 24px); + text-align: center; + font-family: var(--ddd-font-primary, sans-serif); + background: var(--ddd-theme-default-white, white); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + } + + rpg-character { + display: block; + margin: 0 0 var(--ddd-spacing-4, 16px) 0; + width: 120px; + height: 120px; + } + + #errorText { + color: var(--ddd-theme-default-original87Pink, #e4007c); + font-size: var(--ddd-font-size-xs, 14px); + margin: var(--ddd-spacing-2, 8px) 0; + min-height: var(--ddd-spacing-5, 20px); + font-weight: var(--ddd-font-weight-medium, 500); + } + + #inputcontainer { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: var(--ddd-spacing-4, 16px); + } + + .form-group { + width: 100%; + position: relative; + } + + input { + width: 100%; + padding: var(--ddd-spacing-3, 12px); + border: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-slateGray, #666); + border-radius: var(--ddd-radius-sm, 4px); + font-size: var(--ddd-font-size-s, 16px); + font-family: var(--ddd-font-primary, sans-serif); + box-sizing: border-box; + transition: border-color 0.2s ease; + background-color: var(--ddd-theme-default-limestoneMaxLight, #f5f5f5); + color: var(--ddd-theme-default-coalyGray, #444); + } + + input:focus { + outline: none; + border-color: var(--ddd-theme-default-nittanyNavy, #001e44); + } + + input::placeholder { + color: var(--ddd-theme-default-slateGray, #666); + text-transform: capitalize; + } + + button { + padding: var(--ddd-spacing-3, 12px) var(--ddd-spacing-4, 16px); + border-radius: var(--ddd-radius-sm, 4px); + font-size: var(--ddd-font-size-s, 16px); + font-weight: var(--ddd-font-weight-medium, 500); + font-family: var(--ddd-font-primary, sans-serif); + cursor: pointer; + transition: all 0.2s ease; + border: none; + display: flex; + align-items: center; + justify-content: center; + gap: var(--ddd-spacing-2, 8px); + width: 100%; + min-height: var(--ddd-spacing-10, 40px); + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + } + + button:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + button:hover:not(:disabled) { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + transform: translateY(-1px); + box-shadow: var(--ddd-boxShadow-md); + } + + .notyou { + padding: var(--ddd-spacing-2, 8px); + font-size: var(--ddd-font-size-s, 16px); + color: var(--ddd-theme-default-coalyGray, #444); + } + + .notyou a { + color: var(--ddd-theme-default-nittanyNavy, #001e44); + text-decoration: underline; + cursor: pointer; + font-weight: var(--ddd-font-weight-medium, 500); + } + + .notyou a:hover { + color: var(--ddd-theme-default-keystoneYellow, #ffd100); + } + + .visibility-icon { + position: absolute; + right: var(--ddd-spacing-3, 12px); + top: 50%; + transform: translateY(-50%); + background: transparent; + border: none; + color: var(--ddd-theme-default-slateGray, #666); + cursor: pointer; + padding: var(--ddd-spacing-1, 4px); + border-radius: var(--ddd-radius-xs, 2px); + transition: color 0.2s ease; + --simple-icon-width: var(--ddd-icon-xs, 16px); + --simple-icon-height: var(--ddd-icon-xs, 16px); + } + + .visibility-icon:hover { + color: var(--ddd-theme-default-nittanyNavy, #001e44); + } + + .external { + text-align: center; + width: 100%; + margin-top: var(--ddd-spacing-4, 16px); + } + + @media (max-width: 600px) { + :host { + padding: var(--ddd-spacing-4, 16px); + } + + rpg-character { + width: 80px; + height: 80px; + } + + button { + font-size: var(--ddd-font-size-xs, 14px); + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + } + } + `, + ]; + } + + // eslint-disable-next-line class-methods-use-this + checkUsername() { + // eslint-disable-next-line prefer-destructuring + const value = this.shadowRoot.querySelector("#username").value; + this.hidePassword = false; + this.errorMSG = ""; + this.username = value; + store.appEl.playSound("click2"); + setTimeout(() => { + this.shadowRoot.querySelector("input").focus(); + }, 0); + } + + // eslint-disable-next-line class-methods-use-this + async checkPassword() { + store.appEl.playSound("click2"); + // eslint-disable-next-line prefer-destructuring + const value = this.shadowRoot.querySelector("#password").value; + // attempt login and wait for response from the jwt-login tag via + // jwt-logged-in event @see _jwtLoggedIn + globalThis.dispatchEvent( + new CustomEvent("jwt-login-login", { + composed: true, + bubbles: true, + cancelable: false, + detail: { + username: this.username, + password: value, + }, + }), + ); + } + + // eslint-disable-next-line class-methods-use-this + reset() { + this.errorMSG = ""; + this.username = ""; + this.hasPass = false; + this.hidePassword = true; + } + + nameChange() { + this.username = this.shadowRoot.querySelector("#username").value; + } + + connectedCallback() { + super.connectedCallback(); + globalThis.addEventListener("jwt-logged-in", this._jwtLoggedIn.bind(this), { + signal: this.windowControllers.signal, + }); + globalThis.addEventListener( + "jwt-login-login-failed", + this._jwtLoginFailed.bind(this), + { signal: this.windowControllers.signal }, + ); + } + + disconnectedCallback() { + this.windowControllers.abort(); + super.disconnectedCallback(); + } + // implies that it failed to connect via the login credentials + _jwtLoginFailed(e) { + this.hidePassword = true; + this.errorMSG = "Invalid Username or Password"; + store.appEl.playSound("error"); + } + _jwtLoggedIn(e) { + if (e.detail) { + store.user = { + name: this.username, + }; + store.appEl.playSound("success"); + this.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + cancelable: true, + detail: {}, + }), + ); + store.toast(`Welcome ${this.username}! Let's go!`, 5000, { + hat: "construction", + }); + // just to be safe + store.appEl.reset(); + } + } + + passChange(e) { + const value = this.shadowRoot.querySelector("#password").value; + if (value) { + this.hasPass = true; + } else { + this.hasPass = false; + } + } + toggleViewPass(e) { + const password = this.shadowRoot.querySelector("#password"); + const type = + password.getAttribute("type") === "password" ? "text" : "password"; + password.setAttribute("type", type); + e.target.icon = type === "text" ? "lrn:visible" : "lrn:view-off"; + } + + render() { + return html` + +

${this.errorMSG}

+
+ ${this.hidePassword + ? html`
+ +
+ ` + : html`
+ Welcome back, ${this.username}! + Not you? +
+
+ + +
+ `} +
+ +
+
+ `; + } +} +customElements.define(AppHaxSiteLogin.tag, AppHaxSiteLogin); diff --git a/elements/app-hax/lib/v2/app-hax-steps.js b/elements/app-hax/lib/v2/app-hax-steps.js new file mode 100644 index 0000000000..35f805bef6 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-steps.js @@ -0,0 +1,1272 @@ +/* eslint-disable lit/attribute-value-entities */ +/* eslint-disable lit/binding-positions */ +/* eslint-disable import/no-unresolved */ +/* eslint-disable import/no-extraneous-dependencies */ +/* eslint-disable class-methods-use-this */ +import { html, css, unsafeCSS } from "lit"; +import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; +import { autorun, toJS } from "mobx"; +import { store } from "./AppHaxStore.js"; +import { localStorageSet } from "@haxtheweb/utils/utils.js"; +import "scrollable-component/index.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import { MicroFrontendRegistry } from "@haxtheweb/micro-frontend-registry/micro-frontend-registry.js"; +import { enableServices } from "@haxtheweb/micro-frontend-registry/lib/microServices.js"; +import "./app-hax-site-button.js"; +import "./app-hax-hat-progress.js"; +import "./app-hax-button.js"; + +const homeIcon = new URL("../assets/images/Home.svg", import.meta.url).href; +const disabledCircle = new URL( + "../assets/images/DisabledCircle.svg", + import.meta.url, +).href; +const transparentCircle = new URL( + "../assets/images/TransparentCircle.svg", + import.meta.url, +).href; +const enabledCircle = new URL( + "../assets/images/EnabledCircle.svg", + import.meta.url, +).href; + +const themeContext = { + collection: ["collections-theme", "bootstrap-theme"], + blog: ["haxor-slevin"], + course: ["clean-one", "clean-two", "learn-two-theme"], + website: ["polaris-flex-theme"], + training: ["training-theme"], + import: ["clean-one", "clean-two", "learn-two-theme"], +}; +export class AppHaxSteps extends SimpleColors { + static get tag() { + return "app-hax-steps"; + } + + constructor() { + super(); + this.unlockComingSoon = false; + this.unlockTerrible = false; + this.windowControllers = new AbortController(); + this.nameTyped = ""; + this.stepRoutes = []; + this._progressReady = false; + this.step = null; + this.loaded = false; + this.themeNames = []; + this.appSettings = {}; + autorun(() => { + this.appSettings = toJS(store.appSettings); + const contextKey = toJS(store.site.structure); + this.themeNames = Object.keys(this.appSettings.themes).filter( + (value) => + contextKey && + themeContext[contextKey] && + themeContext[contextKey].includes(value), + ); + }); + autorun(() => { + this.dark = toJS(store.darkMode); + }); + autorun(() => { + localStorageSet("app-hax-step", toJS(store.step)); + }); + autorun(() => { + localStorageSet("app-hax-site", toJS(store.site)); + this.step = store.stepTest(this.step); + }); + autorun(() => { + if (toJS(store.createSiteSteps) && toJS(store.location)) { + this.step = store.stepTest(this.step); + } + }); + // routes, but only the ones that have a step property + autorun(() => { + const routes = toJS(store.routes); + this.stepRoutes = routes.filter((item) => item.step); + }); + } + + static get properties() { + return { + ...super.properties, + step: { type: Number, reflect: true }, + stepRoutes: { type: Array }, + themeNames: { type: Array }, + unlockComingSoon: { + type: Boolean, + reflect: true, + attribute: "unlock-coming-soon", + }, + unlockTerrible: { + type: Boolean, + reflect: true, + attribute: "unlock-terrible", + }, + loaded: { type: Boolean, reflect: true }, + appSettings: { type: Object }, + nameTyped: { type: String }, + }; + } + + // step 1 + chooseStructure(e) { + if (!e.target.comingSoon) { + const { value } = e.target; + store.site.structure = value; + // @note for now, auto select type and theme if making a course + // we might want to revisit this in the future + if (value === "course") { + store.site.type = "own"; + store.site.theme = "clean-one"; + } + if (value === "blog") { + store.site.type = "own"; + store.site.theme = "haxor-slevin"; + } + if (value === "collection") { + store.site.type = "own"; + store.site.theme = "collections-theme"; + } + if (value === "website") { + store.site.type = "own"; + store.site.theme = "polaris-flex-theme"; + } + if (value === "training") { + store.site.type = "own"; + store.site.theme = "training-theme"; + } + store.appEl.playSound("click2"); + } + } + + // step 2 + chooseType(e) { + if (!e.target.comingSoon) { + const { type } = e.target; + store.site.type = type; + store.appEl.playSound("click2"); + } + } + // step 2, doc import + async docxImport(e) { + if (!e.target.comingSoon) { + const { type } = e.target; + import( + "@haxtheweb/file-system-broker/lib/docx-file-system-broker.js" + ).then(async (e) => { + // enable core services + enableServices(["haxcms"]); + // get the broker for docx selection + const broker = globalThis.FileSystemBroker.requestAvailability(); + const file = await broker.loadFile("docx"); + // tee up as a form for upload + const formData = new FormData(); + formData.append("method", "site"); // this is a site based importer + formData.append("type", toJS(store.site.structure)); + formData.append("upload", file); + this.setProcessingVisual(); + const response = await MicroFrontendRegistry.call( + "@haxcms/docxToSite", + formData, + ); + store.toast(`Processed!`, 300); + // must be a valid response and have at least SOME html to bother attempting + if ( + response.status == 200 && + response.data && + response.data.contents != "" + ) { + store.items = response.data.items; + if (response.data.files) { + store.itemFiles = response.data.files; + } + // invoke a file broker for a docx file + // send to the endpoint and wait + // if it comes back with content, then we engineer details off of it + this.nameTyped = response.data.filename + .replace(".docx", "") + .replace("outline", "") + .replace(/\s/g, "") + .replace(/-/g, "") + .toLowerCase(); + setTimeout(() => { + this.shadowRoot.querySelector("#sitename").value = this.nameTyped; + this.shadowRoot.querySelector("#sitename").select(); + }, 800); + store.site.type = type; + store.site.theme = "clean-one"; + store.appEl.playSound("click2"); + } else { + store.appEl.playSound("error"); + store.toast(`File did not return valid HTML structure`); + } + }); + } + } + // evolution import + async evoImport(e) { + if (!e.target.comingSoon) { + const { type } = e.target; + import("@haxtheweb/file-system-broker/file-system-broker.js").then( + async (e) => { + // enable core services + enableServices(["haxcms"]); + // get the broker for docx selection + const broker = globalThis.FileSystemBroker.requestAvailability(); + const file = await broker.loadFile("zip"); + // tee up as a form for upload + const formData = new FormData(); + formData.append("method", "site"); // this is a site based importer + formData.append("type", toJS(store.site.structure)); + formData.append("upload", file); + // local end point + stupid JWT thing + this.setProcessingVisual(); + const response = await MicroFrontendRegistry.call( + "@haxcms/evolutionToSite", + formData, + null, + null, + "?jwt=" + toJS(store.AppHaxAPI.jwt), + ); + store.toast(`Processed!`, 300); + // must be a valid response and have at least SOME html to bother attempting + if ( + response.status == 200 && + response.data && + response.data.contents != "" + ) { + store.items = response.data.items; + // invoke a file broker for a docx file + // send to the endpoint and wait + // if it comes back with content, then we engineer details off of it + this.nameTyped = response.data.filename + .replace(".zip", "") + .replace("outline", "") + .replace(/\s/g, "") + .replace(/-/g, "") + .toLowerCase(); + setTimeout(() => { + this.shadowRoot.querySelector("#sitename").value = this.nameTyped; + this.shadowRoot.querySelector("#sitename").select(); + }, 800); + store.site.type = type; + store.site.theme = "clean-one"; + store.appEl.playSound("click2"); + } else { + store.appEl.playSound("error"); + store.toast(`File did not return valid HTML structure`); + } + }, + ); + } + } + // gitbook import endpoint + async gbImport(e) { + if (!e.target.comingSoon) { + const { type } = e.target; + let gbURL = globalThis.prompt("URL for the Gitbook repo"); + enableServices(["haxcms"]); + this.setProcessingVisual(); + const response = await MicroFrontendRegistry.call( + "@haxcms/gitbookToSite", + { md: gbURL }, + ); + store.toast(`Processed!`, 300); + // must be a valid response and have at least SOME html to bother attempting + if ( + response.status == 200 && + response.data && + response.data.contents != "" + ) { + store.items = response.data.items; + if (response.data.files) { + store.itemFiles = response.data.files; + } + // invoke a file broker for a docx file + // send to the endpoint and wait + // if it comes back with content, then we engineer details off of it + this.nameTyped = response.data.filename + .replace(/\s/g, "") + .replace(/-/g, "") + .toLowerCase(); + setTimeout(() => { + this.shadowRoot.querySelector("#sitename").value = this.nameTyped; + this.shadowRoot.querySelector("#sitename").select(); + }, 800); + store.site.type = type; + store.site.theme = "clean-one"; + store.appEl.playSound("click2"); + } else { + store.appEl.playSound("error"); + store.toast(`Repo did not return valid structure`); + } + } + } + async importFromURL(e) { + const { type, prompt, callback, param } = e.target; + if (!e.target.comingSoon) { + let promptUrl = globalThis.prompt(prompt); + enableServices(["haxcms"]); + this.setProcessingVisual(); + const params = {}; + params[param] = promptUrl; + const response = await MicroFrontendRegistry.call(callback, params); + store.toast(`Processed!`, 300); + // must be a valid response and have at least SOME html to bother attempting + if ( + response.status == 200 && + response.data && + response.data.contents != "" + ) { + store.items = response.data.items; + if (response.data.files) { + store.itemFiles = response.data.files; + } + // invoke a file broker for a docx file + // send to the endpoint and wait + // if it comes back with content, then we engineer details off of it + this.nameTyped = response.data.filename + .replace(/\s/g, "") + .replace(/-/g, "") + .toLowerCase(); + setTimeout(() => { + this.shadowRoot.querySelector("#sitename").value = this.nameTyped; + this.shadowRoot.querySelector("#sitename").select(); + }, 800); + store.site.type = type; + store.site.theme = "clean-one"; + store.appEl.playSound("click2"); + } else { + store.appEl.playSound("error"); + store.toast(`Repo did not return valid structure`); + } + } + } + // notion import endpoint + async notionImport(e) { + if (!e.target.comingSoon) { + const { type } = e.target; + let notionUrl = globalThis.prompt("URL for the Github Notion repo"); + enableServices(["haxcms"]); + this.setProcessingVisual(); + const response = await MicroFrontendRegistry.call( + "@haxcms/notionToSite", + { repoUrl: notionUrl }, + ); + store.toast(`Processed!`, 300); + // must be a valid response and have at least SOME html to bother attempting + if ( + response.status == 200 && + response.data && + response.data.contents != "" + ) { + store.items = response.data.items; + if (response.data.files) { + store.itemFiles = response.data.files; + } + // invoke a file broker for a docx file + // send to the endpoint and wait + // if it comes back with content, then we engineer details off of it + this.nameTyped = response.data.filename + .replace(/\s/g, "") + .replace(/-/g, "") + .toLowerCase(); + setTimeout(() => { + this.shadowRoot.querySelector("#sitename").value = this.nameTyped; + this.shadowRoot.querySelector("#sitename").select(); + }, 800); + store.site.type = type; + store.site.theme = "clean-one"; + store.appEl.playSound("click2"); + } else { + store.appEl.playSound("error"); + store.toast(`Repo did not return valid structure`); + } + } + } + // pressbooks import endpoint + async pressbooksImport(e) { + if (!e.target.comingSoon) { + const { type } = e.target; + import( + "@haxtheweb/file-system-broker/lib/docx-file-system-broker.js" + ).then(async (e) => { + // enable core services + enableServices(["haxcms"]); + // get the broker for docx selection + const broker = globalThis.FileSystemBroker.requestAvailability(); + const file = await broker.loadFile("html"); + // tee up as a form for upload + const formData = new FormData(); + formData.append("method", "site"); // this is a site based importer + formData.append("type", toJS(store.site.structure)); + formData.append("upload", file); + this.setProcessingVisual(); + const response = await MicroFrontendRegistry.call( + "@haxcms/pressbooksToSite", + formData, + ); + store.toast(`Processed!`, 300); + // must be a valid response and have at least SOME html to bother attempting + if ( + response.status == 200 && + response.data && + response.data.contents != "" + ) { + store.items = response.data.items; + if (response.data.files) { + store.itemFiles = response.data.files; + } + // invoke a file broker for a html file + // send to the endpoint and wait + // if it comes back with content, then we engineer details off of it + this.nameTyped = response.data.filename + .replace(".html", "") + .replace("outline", "") + .replace(/\s/g, "") + .replace(/-/g, "") + .toLowerCase(); + setTimeout(() => { + this.shadowRoot.querySelector("#sitename").value = this.nameTyped; + this.shadowRoot.querySelector("#sitename").select(); + }, 800); + store.site.type = type; + store.site.theme = "clean-one"; + store.appEl.playSound("click2"); + } else { + store.appEl.playSound("error"); + store.toast(`File did not return valid HTML structure`); + } + }); + } + } + // makes guy have hat on, shows it's doing something + setProcessingVisual() { + let loadingIcon = globalThis.document.createElement("simple-icon-lite"); + loadingIcon.icon = "hax:loading"; + loadingIcon.style.setProperty("--simple-icon-height", "40px"); + loadingIcon.style.setProperty("--simple-icon-width", "40px"); + loadingIcon.style.height = "150px"; + loadingIcon.style.marginLeft = "8px"; + store.toast(`Processing`, 60000, { + hat: "construction", + slot: loadingIcon, + }); + } + // step 3 + chooseTheme(e) { + if (!e.target.comingSoon) { + const { value } = e.target; + store.site.theme = value; + store.appEl.playSound("click2"); + } + } + + // step 4 + chooseName() { + if (this.nameTyped !== "") { + const value = this.shadowRoot.querySelector("#sitename").value; + store.site.name = value; + store.appEl.playSound("click2"); + } + } + + progressReady(e) { + if (e.detail) { + this._progressReady = true; + if (this.step === 5) { + setTimeout(() => { + this.shadowRoot.querySelector("app-hax-hat-progress").process(); + }, 300); + } + } + } + + updated(changedProperties) { + if (super.updated) { + super.updated(changedProperties); + } + changedProperties.forEach((oldValue, propName) => { + // set input field to whats in store if we have it + if (this.step === 4 && propName === "step" && this.shadowRoot) { + this.shadowRoot.querySelector("#sitename").value = toJS( + store.site.name, + ); + } + // progress + if ( + this.step === 5 && + propName === "step" && + this.shadowRoot && + this._progressReady + ) { + setTimeout(() => { + this.shadowRoot.querySelector("app-hax-hat-progress").process(); + }, 600); + } + // update the store for step when it changes internal to our step flow + if (propName === "step") { + store.step = this.step; + } + if (propName === "unlockTerrible" && this[propName]) { + Object.keys(themeContext).forEach((key) => { + themeContext[key] = [ + ...themeContext[key], + "terrible-themes", + "terrible-productionz-themes", + "terrible-outlet-themes", + "terrible-best-themes", + "terrible-resume-themes", + ]; + }); + const contextKey = toJS(store.site.structure); + this.themeNames = Object.keys(this.appSettings.themes).filter( + (value) => + contextKey && + themeContext[contextKey] && + themeContext[contextKey].includes(value), + ); + } + }); + } + + connectedCallback() { + super.connectedCallback(); + globalThis.addEventListener("resize", this.maintainScroll.bind(this), { + signal: this.windowControllers.signal, + }); + globalThis.addEventListener("popstate", this.popstateListener.bind(this), { + signal: this.windowControllers.signal, + }); + } + + disconnectedCallback() { + this.windowControllers.abort(); + super.disconnectedCallback(); + } + + // see if user navigates forward or backward while in app + popstateListener(e) { + // filter out vaadin link clicks which have a state signature + if (e.type === "popstate" && e.state === null) { + // a lot going on here, just to be safe + try { + // the delay allows clicking for step to change, process, and then testing it + setTimeout(() => { + const link = e.target.document.location.pathname.split("/").pop(); + // other links we don't care about validating state + if (link.includes("createSite")) { + const step = parseInt(link.replace("createSite-step-", "")); + if (step < store.stepTest(step)) { + this.shadowRoot.querySelector("#link-step-" + step).click(); + } else if (step > store.stepTest(step)) { + store.toast(`Please select an option`); + this.step = store.stepTest(step); + // forces state by maintaining where we are + this.shadowRoot.querySelector("#link-step-" + this.step).click(); + } + } + }, 0); + } catch (e) {} + } + } + + // account for resizing + maintainScroll() { + if (this.shadowRoot && this.step) { + this.scrollToThing(`#step-${this.step}`, { + behavior: "instant", + block: "start", + inline: "nearest", + }); + // account for an animated window drag... stupid. + setTimeout(() => { + this.scrollToThing(`#step-${this.step}`, { + behavior: "instant", + block: "start", + inline: "nearest", + }); + }, 100); + } + } + + firstUpdated(changedProperties) { + if (super.firstUpdated) { + super.firstUpdated(changedProperties); + } + setTimeout(() => { + // ensure paint issues not a factor for null step + if (this.step === null) { + this.step = 1; + } + this.scrollToThing(`#step-${this.step}`, { + behavior: "instant", + block: "start", + inline: "nearest", + }); + }, 100); + + autorun(() => { + // verify we are in the site creation process + if (toJS(store.createSiteSteps) && toJS(store.appReady)) { + const location = toJS(store.location); + if (location.route && location.route.step && location.route.name) { + // account for an animated window drag... stupid. + setTimeout(() => { + this.scrollToThing("#".concat(location.route.name), { + behavior: "smooth", + block: "start", + inline: "nearest", + }); + /// just for step 4 since it has an input + if (location.route.step === 4 && store.stepTest(4) === 4) { + setTimeout(() => { + this.shadowRoot.querySelector("#sitename").focus(); + this.scrollToThing(`#step-4`, { + behavior: "instant", + block: "start", + inline: "nearest", + }); + }, 800); + } + }, 300); // this delay helps w/ initial paint timing but also user perception + // there's a desire to have a delay especialy when tapping things of + // about 300ms + } + } + }); + autorun(() => { + if ( + this.shadowRoot && + toJS(store.createSiteSteps) && + toJS(store.appReady) + ) { + const activeItem = toJS(store.activeItem); + if ( + activeItem && + activeItem.name && + activeItem.step && + !this.__overrideProgression + ) { + this.shadowRoot + .querySelector("#link-".concat(activeItem.name)) + .click(); + } + } + }); + } + + /** + * Yet another reason Apple doesn't let us have nice things. + * This detects the NONSTANDARD BS VERSION OF SCROLLINTOVIEW + * and then ensures that it incorrectly calls to scroll into view + * WITHOUT the wonderful params that ALL OTHER BROWSERS ACCEPT + * AND MAKE OUR LIVES SO WONDERFUL TO SCROLL TO THINGS SMOOTHLY + */ + scrollToThing(sel, props) { + const isSafari = globalThis.safari !== undefined; + if ( + this.shadowRoot.querySelector(".carousel-with-snapping-item.active-step") + ) { + this.shadowRoot + .querySelector(".carousel-with-snapping-item.active-step") + .classList.remove("active-step"); + } + if (isSafari) { + this.shadowRoot.querySelector(sel).scrollIntoView(); + } else { + this.shadowRoot.querySelector(sel).scrollIntoView(props); + } + this.shadowRoot.querySelector(sel).classList.add("active-step"); + } + + static get styles() { + return [ + super.styles, + css` + :host { + display: block; + } + scrollable-component { + --scrollbar-width: 0px; + --scrollbar-height: 0px; + --scrollbar-padding: 0; + --viewport-overflow-x: hidden; + overflow: hidden; + } + #grid-container { + display: grid; + grid-template-columns: 160px 160px 160px; + background: transparent; + } + .carousel-with-snapping-track { + display: grid; + grid-auto-flow: column; + grid-gap: 30px; + } + .carousel-with-snapping-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: normal; + scroll-snap-align: center; + scroll-snap-stop: always; + scrollbar-gutter: stable; + width: var(--viewport-width); + font-size: 1.5rem; + text-align: center; + overflow-x: hidden; + max-height: 60vh; + padding-top: 1vh; + } + #step-links { + padding: 0; + margin: 0; + } + ul, + li { + list-style: none; + } + li { + vertical-align: middle; + display: inline-flex; + margin: 5px; + } + li.step { + border-radius: 50%; + background-color: transparent; + } + li a { + font-size: 12px; + color: var(--simple-colors-default-theme-grey-12, white); + text-decoration: none; + padding: 5px; + width: 20px; + height: 20px; + line-height: 20px; + margin: 0; + display: block; + border: 0; + border-radius: 50%; + background-repeat: no-repeat; + background-size: 30px 30px; + background-color: var(--simple-colors-default-theme-grey-1, white); + background-image: url("${unsafeCSS(enabledCircle)}"); + transition: + 0.3s ease-in-out background, + 0.3s ease-in-out color; + transition-delay: 0.6s, 0.3s; + } + li a[disabled] { + background-image: url("${unsafeCSS(disabledCircle)}"); + pointer-events: none; + color: var(--simple-colors-default-theme-grey-7, grey); + user-select: none; + } + li[disabled] { + background-color: grey; + } + li.active-step a { + background-color: orange; + background-image: url("${unsafeCSS(transparentCircle)}"); + } + app-hax-button { + padding: 10px 0px 10px 0px; + background: transparent; + } + #theme-container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + } + img { + pointer-events: none; + } + #themeContainer { + width: 70vw; + height: 55vh; + } + .theme-button { + background-color: transparent; + color: var(--simple-colors-default-theme-grey-12, white); + border: none; + margin: 8px; + padding: 8px; + width: 245px; + } + + .theme-button div { + font-size: 14px; + margin-top: 12px; + } + .theme-button:focus, + .theme-button:hover { + outline: 4px solid var(--app-hax-accent-color, var(--accent-color)); + outline-offset: 4px; + background-color: transparent; + border: none; + cursor: pointer; + } + #sitename { + font-size: 32px; + padding: 8px; + width: 40vw; + } + #homebtn { + --simple-icon-height: 30px; + --simple-icon-width: 30px; + border-radius: 50%; + cursor: pointer; + background-color: var(--simple-colors-default-theme-grey-1, white); + } + .homelnk { + background-image: none; + display: flex; + padding: 0; + margin: 0; + height: 30px; + width: 30px; + } + app-hax-site-button { + justify-content: center; + --app-hax-site-button-width: 35vw; + --app-hax-site-button-min-width: 240px; + } + app-hax-hat-progress { + height: 400px; + width: 400px; + display: block; + } + + @media (max-width: 800px) { + .theme-button { + width: unset; + padding: 0; + } + .theme-button div { + font-size: 12px; + margin-top: 8px; + } + .theme-button img { + height: 70px; + } + app-hax-site-button { + width: 320px; + max-width: 60vw; + --app-hax-site-button-font-size: 2.5vw; + } + #sitename { + width: 70vw; + font-size: 20px; + } + #grid-container { + grid-template-columns: 120px 120px 120px; + } + } + @media (max-height: 600px) { + .carousel-with-snapping-item { + padding-top: 4px; + max-height: 57vh; + } + #sitename { + width: 40vw; + font-size: 14px; + } + app-hax-hat-progress { + transform: scale(0.5); + margin-top: -18vh; + } + } + @media (max-width: 500px) { + app-hax-hat-progress { + transform: scale(0.5); + margin-top: -15vh; + } + } + @media (max-height: 400px) { + .carousel-with-snapping-item { + padding-top: 4px; + max-height: 40vh; + } + app-hax-hat-progress { + transform: scale(0.3); + } + .carousel-with-snapping-item.active-step app-hax-hat-progress { + position: fixed; + top: 20%; + left: 20%; + } + } + `, + ]; + } + + progressFinished(e) { + if (e.detail) { + this.loaded = true; + store.appEl.playSound("success"); + // focus the button for going to the site + e.target.shadowRoot.querySelector(".game").focus(); + this.scrollToThing(`#step-${this.step}`, { + behavior: "instant", + block: "start", + inline: "nearest", + }); + } + } + + typeKey() { + this.nameTyped = this.shadowRoot.querySelector("#sitename").value; + } + keydown(e) { + // some trapping for common characters that make us sad + if ( + [ + " ", + "/", + "\\", + "&", + "#", + "?", + "+", + "=", + "{", + "}", + "|", + "^", + "~", + "[", + "]", + "`", + '"', + "'", + ].includes(e.key) + ) { + store.appEl.playSound("error"); + store.toast(`"${e.key}" is not allowed. Use - or _`); + e.preventDefault(); + } else if (e.key === "Enter") { + this.chooseName(); + } else if ( + ["ArrowUp", "ArrowRight", "ArrowDown", "ArrowLeft"].includes(e.key) + ) { + // do nothing, directional keys for modifying word + } else { + store.appEl.playSound("click"); + } + } + + stepLinkClick(e) { + const clickedStep = parseInt(e.target.getAttribute("data-step"), 10); + if (this.step < clickedStep) { + e.preventDefault(); + } else if (e.target.getAttribute("data-step") === null) { + store.createSiteSteps = false; + store.appMode = "home"; + this.nameTyped = ""; + store.siteReady = false; + store.site.structure = null; + store.site.type = null; + store.site.theme = null; + store.site.name = null; + } + // means user went backwards + else if (this.step > clickedStep) { + this.nameTyped = ""; + store.siteReady = false; + if (clickedStep === 1) { + store.site.structure = null; + store.site.type = null; + store.site.theme = null; + store.site.name = null; + } else if (clickedStep === 2) { + store.site.type = null; + store.site.theme = null; + store.site.name = null; + } else if (clickedStep === 3) { + store.site.theme = null; + store.site.name = null; + } else if (clickedStep === 4) { + store.site.name = null; + } + this.step = clickedStep; + } + } + + renderTypes(step) { + const structure = toJS(store.site.structure); + var template = html``; + switch (structure) { + case "collection": + template = html` + + + `; + break; + default: + case "course": + template = html` + `; + break; + case "website": + template = html` `; + break; + case "training": + template = html` `; + break; + case "blog": + template = html` `; + break; + case "import": + template = html` + + + + + + + `; + break; + } + return template; + } + + render() { + return html` +
+
    +
  • + ${!toJS(store.isNewUser) + ? html` + + + + Site list + ` + : html``} +
  • + ${this.stepRoutes.map( + (item, index) => + html`
  • + ${index + 1} + Step ${index + 1}: ${item.label} +
  • `, + )} +
+ +
+
+
+ + + + + + +
+
+
+
${this.renderTypes(this.step)}
+
+
+
+ ${this.appSettings && this.appSettings.themes + ? this.themeNames.map( + (themeKey) => html` + + `, + ) + : ``} +
+
+
+ + + +
+
+ +
+
+
+
+ `; + } +} +customElements.define(AppHaxSteps.tag, AppHaxSteps); diff --git a/elements/app-hax/lib/v2/app-hax-toast.js b/elements/app-hax/lib/v2/app-hax-toast.js new file mode 100644 index 0000000000..0b389800c4 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-toast.js @@ -0,0 +1,60 @@ +import { autorun, toJS } from "mobx"; +import { store } from "./AppHaxStore.js"; +import { RPGCharacterToast } from "@haxtheweb/haxcms-elements/lib/core/ui/rpg-character-toast/rpg-character-toast.js"; + +export class AppHaxToast extends RPGCharacterToast { + static get tag() { + return "app-hax-toast"; + } + + constructor() { + super(); + this.windowControllers = new AbortController(); + autorun(() => { + this.userName = toJS(store.user.name); + }); + autorun(() => { + this.darkMode = toJS(store.darkMode); + }); + } + + connectedCallback() { + super.connectedCallback(); + globalThis.addEventListener( + "haxcms-toast-hide", + this.hideSimpleToast.bind(this), + { signal: this.windowControllers.signal }, + ); + + globalThis.addEventListener( + "haxcms-toast-show", + this.showSimpleToast.bind(this), + { signal: this.windowControllers.signal }, + ); + } + + hideSimpleToast(e) { + this.hide(); + } + + /** + * life cycle, element is removed from the DOM + */ + disconnectedCallback() { + this.windowControllers.abort(); + super.disconnectedCallback(); + } +} +customElements.define(AppHaxToast.tag, AppHaxToast); +globalThis.AppHaxToast = globalThis.AppHaxToast || {}; + +globalThis.AppHaxToast.requestAvailability = () => { + if (!globalThis.AppHaxToast.instance) { + globalThis.AppHaxToast.instance = globalThis.document.createElement( + AppHaxToast.tag, + ); + globalThis.document.body.appendChild(globalThis.AppHaxToast.instance); + } + return globalThis.AppHaxToast.instance; +}; +export const AppHaxToastInstance = globalThis.AppHaxToast.requestAvailability(); diff --git a/elements/app-hax/lib/v2/app-hax-top-bar.js b/elements/app-hax/lib/v2/app-hax-top-bar.js new file mode 100644 index 0000000000..33616ccfe5 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-top-bar.js @@ -0,0 +1,130 @@ +// dependencies / things imported +import { LitElement, html, css } from "lit"; +import "./app-hax-wired-toggle.js"; + +// top bar of the UI +export class AppHaxTopBar extends LitElement { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-top-bar"; + } + + constructor() { + super(); + this.editMode = false; + } + + static get properties() { + return { + editMode: { + type: Boolean, + reflect: true, + attribute: "edit-mode", + }, + }; + } + + static get styles() { + return css` + :host { + --bg-color: var(--app-hax-background-color); + --accent-color: var(--app-hax-accent-color); + --top-bar-height: 46px; + display: block; + height: var(--top-bar-height); + } + + /* @media (prefers-color-scheme: dark) { + :root { + --accent-color: white; + color: var(--accent-color); + + } + + :host { + background-color: black; + } + } */ + + .topBar { + overflow: hidden; + background-color: var(--bg-color); + color: var(--accent-color); + height: var(--top-bar-height); + text-align: center; + vertical-align: middle; + border-bottom: 2px solid var(--app-hax-accent-color); + display: grid; + grid-template-columns: 32.5% 35% 32.5%; + transition: border-bottom 0.6s ease-in-out; + } + + /* .topBar > div { + background-color: rgba(255, 255, 255, 0.8); + border: 1px solid black; + } */ + + .topBar .left { + text-align: left; + height: var(--top-bar-height); + vertical-align: text-top; + } + + .topBar .center { + text-align: center; + height: var(--top-bar-height); + vertical-align: text-top; + } + + .topBar .right { + text-align: right; + height: var(--top-bar-height); + vertical-align: text-top; + } + @media (max-width: 640px) { + .topBar .left { + opacity: 0; + pointer-events: none; + } + .topBar .center { + text-align: left; + } + .topBar .right { + text-align: left; + } + #home { + display: none; + } + app-hax-search-bar { + display: none; + } + .topBar { + grid-template-columns: 0% 35% 65%; + display: inline-grid; + } + } + `; + } + + render() { + return html` +
+
+ +
+
+ +
+
+ +
+
+ `; + } +} +customElements.define(AppHaxTopBar.tag, AppHaxTopBar); diff --git a/elements/app-hax/lib/v2/app-hax-use-case-filter.js b/elements/app-hax/lib/v2/app-hax-use-case-filter.js new file mode 100644 index 0000000000..cb8ca78ea4 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-use-case-filter.js @@ -0,0 +1,1517 @@ +/* eslint-disable no-return-assign */ +import { LitElement, html, css } from "lit"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; +import "@haxtheweb/simple-icon/lib/simple-icons.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import { store } from "./AppHaxStore.js"; +import "./app-hax-use-case.js"; +import "./app-hax-search-results.js"; +import "./app-hax-filter-tag.js"; +import "./app-hax-scroll-button.js"; +import "./app-hax-site-creation-modal.js"; + +export class AppHaxUseCaseFilter extends LitElement { + static get tag() { + return "app-hax-use-case-filter"; + } + + constructor() { + super(); + this.windowControllers = new AbortController(); + this.searchTerm = ""; + this.disabled = false; + this.showSearch = false; + this.items = []; + this.filteredItems = []; + this.filteredSites = []; + this.activeFilters = []; + this.filters = []; + this.searchQuery = ""; + this.demoLink = ""; + this.errorMessage = ""; + this.loading = false; + this.selectedCardIndex = null; + this.returningSites = []; + this.allFilters = new Set(); + this.dark = false; + this.isLoggedIn = false; + this.sortOption = "az"; + + // Listen to store changes for dark mode and manifest updates + if (typeof store !== "undefined") { + import("mobx").then(({ autorun, toJS }) => { + autorun(() => { + this.dark = toJS(store.darkMode); + }); + // Watch for appReady AND login state to trigger skeleton loading + let hasLoaded = false; + autorun(() => { + const appReady = toJS(store.appReady); + const loggedIn = toJS(store.isLoggedIn); + + this.isLoggedIn = loggedIn; + + // Trigger skeleton/theme loading when both app is ready and user is logged in + // Only load once per session + if (appReady && loggedIn && !hasLoaded) { + hasLoaded = true; + this.updateSkeletonResults(); + this.updateSiteResults(); + } + }); + // Watch for manifest changes and update site results + autorun(() => { + const manifest = toJS(store.manifest); + if (manifest && manifest.items && manifest.items.length > 0) { + this.updateSiteResults(); + } + }); + }); + } + } + + static get properties() { + return { + searchTerm: { type: String }, + showSearch: { type: Boolean, reflect: true, attribute: "show-search" }, + showFilter: { type: Boolean, reflect: true, attribute: "show-filter" }, + disabled: { type: Boolean, reflect: true }, + items: { type: Array }, + filteredItems: { type: Array }, + filteredSites: { type: Array }, + activeFilters: { type: Array }, + filters: { type: Array }, + searchQuery: { type: String }, + demoLink: { type: String }, + errorMessage: { type: String }, + loading: { type: Boolean }, + selectedCardIndex: { type: Number }, + returningSites: { type: Array }, + allFilters: { attribute: false }, + dark: { type: Boolean, reflect: true }, + isLoggedIn: { type: Boolean }, + sortOption: { type: String, attribute: "sort-option" }, + }; + } + + static get styles() { + return [ + css` + :host { + overflow: hidden; + display: block; + max-width: 100%; + font-family: var(--ddd-font-primary, sans-serif); + } + .contentSection { + display: flex; + align-items: flex-start; + justify-content: flex-start; + gap: var(--ddd-spacing-12, 48px); + width: 100%; + margin: 0; + padding: 0; + box-sizing: border-box; + } + .leftSection, + .rightSection { + display: flex; + flex-direction: column; + flex: 1 1 0; + } + .leftSection { + width: 240px; + min-width: 200px; + max-width: 260px; + margin-left: 0; + margin-right: var(--ddd-spacing-1, 4px); + padding-top: 0; + box-sizing: border-box; + align-self: flex-start; + } + .rightSection { + flex: 1; + min-width: 0; + box-sizing: border-box; + display: flex; + flex-direction: column; + overflow: visible; + } + .template-results { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + width: 100%; + min-height: 330px; + box-sizing: border-box; + gap: var(--ddd-spacing-2, 16px); + } + #returnToSection { + width: 100%; + } + #returnToSection app-hax-search-results { + width: 100%; + min-height: 280px; + box-sizing: border-box; + height: 300px; + } + :host(:not([show-filter])) app-hax-search-results { + width: 100%; + } + + h2, + .returnTo h2, + .startNew h2 { + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-l, 24px); + color: var(--app-hax-accent-color, var(--accent-color)); + margin: 0 0 var(--ddd-spacing-4, 16px) 0; + } + .startNew, + .returnTo { + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + margin: 0; + } + .upper-filter { + margin-bottom: var(--ddd-spacing-4, 16px); + position: relative; + display: inline-block; + width: 100%; + } + input[type="text"] { + width: 100%; + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-2, 8px) + var(--ddd-spacing-2, 8px) var(--ddd-spacing-8, 32px); + font-size: var(--ddd-font-size-xs, 12px); + border-radius: var(--ddd-radius-sm, 4px); + border: var(--ddd-border-xs, 1px solid); + border-color: var(--ddd-theme-default-slateGray, #666); + background: var(--ddd-theme-default-white, white); + color: var(--ddd-theme-default-coalyGray, #222); + transition: all 0.2s ease; + box-sizing: border-box; + font-family: var(--ddd-font-primary, sans-serif); + margin: 0; + min-height: var(--ddd-spacing-8, 32px); + } + :host([dark]) input[type="text"], + body.dark-mode input[type="text"] { + background: var(--ddd-theme-default-coalyGray, #333); + color: var(--ddd-theme-default-white, white); + border-color: var(--ddd-theme-default-slateGray, #666); + } + input[type="text"]:focus { + border: var(--ddd-border-md, 2px solid); + border-color: var(--ddd-theme-default-keystoneYellow, #ffd100); + background: var(--ddd-theme-default-white, white); + outline: none; + } + :host([dark]) input[type="text"]:focus, + body.dark-mode input[type="text"]:focus { + background: var(--ddd-theme-default-coalyGray, #333); + border-color: var(--ddd-theme-default-keystoneYellow, #ffd100); + } + .search-icon { + position: absolute; + left: var(--ddd-spacing-2, 8px); + top: 50%; + transform: translateY(-50%); + font-size: var(--ddd-font-size-xs, 14px); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + pointer-events: none; + z-index: 1; + --simple-icon-width: var(--ddd-icon-3xs, 20px); + --simple-icon-height: var(--ddd-icon-3xs, 20px); + } + :host([dark]) .search-icon, + body.dark-mode .search-icon { + color: var(--ddd-theme-default-white, white); + } + .filter { + position: relative; + top: 0; + display: flex; + flex-direction: column; + gap: var(--ddd-spacing-5, 20px); + background: var(--ddd-theme-default-white, white); + border-radius: var(--ddd-radius-lg, 12px); + box-shadow: var(--ddd-boxShadow-lg); + border: var(--ddd-border-xs, 1px solid); + border-color: var(--ddd-theme-default-slateGray, #666); + padding: var(--ddd-spacing-6, 24px); + margin-top: 0; + margin-bottom: 0; + box-sizing: border-box; + font-family: var(--ddd-font-primary, sans-serif); + transition: box-shadow 0.2s ease; + } + :host([dark]) .filter, + body.dark-mode .filter { + background: var(--ddd-theme-default-coalyGray, #222); + border-color: var(--ddd-theme-default-slateGray, #666); + color: var(--ddd-theme-default-white, white); + } + .filter:hover { + box-shadow: var(--ddd-boxShadow-xl); + } + .filterButtons { + display: flex; + flex-direction: column; + gap: var(--ddd-spacing-3, 12px); + margin-top: 0; + border: none; + padding: 0; + margin: 0; + } + .filter-btn { + display: flex; + align-items: center; + justify-content: flex-start; + gap: var(--ddd-spacing-1, 4px); + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + border-radius: var(--ddd-radius-sm, 4px); + border: var(--ddd-border-xs, 1px solid) transparent; + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + font-size: var(--ddd-font-size-3xs, 11px); + font-family: var(--ddd-font-primary, sans-serif); + font-weight: var(--ddd-font-weight-medium, 500); + cursor: pointer; + box-shadow: var(--ddd-boxShadow-sm); + transition: all 0.2s ease; + outline: none; + min-height: var(--ddd-spacing-7, 28px); + text-align: left; + } + :host([dark]) .filter-btn, + body.dark-mode .filter-btn { + background: var(--ddd-theme-default-slateGray, #444); + color: var(--ddd-theme-default-white, white); + } + .filter-btn.active, + .filter-btn:active { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + border-color: var(--ddd-theme-default-keystoneYellow, #ffd100); + box-shadow: var(--ddd-boxShadow-md); + } + :host([dark]) .filter-btn.active, + :host([dark]) .filter-btn:active, + body.dark-mode .filter-btn.active, + body.dark-mode .filter-btn:active { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + border-color: var(--ddd-theme-default-white, white); + } + .filter-btn:hover, + .filter-btn:focus { + background: var(--ddd-theme-default-slateGray, #666); + color: var(--ddd-theme-default-white, white); + transform: translateY(-1px); + } + .filter-btn:focus { + outline: var(--ddd-border-md, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + outline-offset: var(--ddd-spacing-1, 2px); + } + :host([dark]) .filter-btn:hover, + :host([dark]) .filter-btn:focus, + body.dark-mode .filter-btn:hover, + body.dark-mode .filter-btn:focus { + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + } + .filter-btn .icon { + font-size: var(--ddd-font-size-3xs, 12px); + color: inherit; + display: flex; + align-items: center; + flex-shrink: 0; + width: var(--ddd-icon-3xs, 20px); + height: var(--ddd-icon-3xs, 20px); + } + .filter-btn .icon simple-icon-lite { + color: inherit; + --simple-icon-width: var(--ddd-icon-3xs, 20px); + --simple-icon-height: var(--ddd-icon-3xs, 20px); + } + .filter-btn.active .icon, + .filter-btn.active .icon simple-icon-lite { + color: inherit; + } + :host([dark]) .filter-btn.active .icon, + :host([dark]) .filter-btn.active .icon simple-icon-lite, + body.dark-mode .filter-btn.active .icon, + body.dark-mode .filter-btn.active .icon simple-icon-lite { + color: inherit; + } + .filter-btn:hover .icon simple-icon-lite, + .filter-btn:focus .icon simple-icon-lite { + color: inherit; + } + input[type="checkbox"] { + display: none; + } + .sort-control { + margin-top: var(--ddd-spacing-2, 8px); + margin-bottom: var(--ddd-spacing-3, 12px); + display: flex; + flex-direction: column; + gap: var(--ddd-spacing-1, 4px); + } + .sort-label { + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-3xs, 11px); + color: var(--ddd-theme-default-coalyGray, #222); + } + :host([dark]) .sort-label, + body.dark-mode .sort-label { + color: var(--ddd-theme-default-limestoneGray, #f5f5f5); + } + .sort-select { + width: 100%; + padding: var(--ddd-spacing-1, 4px) var(--ddd-spacing-2, 8px); + font-size: var(--ddd-font-size-3xs, 11px); + border-radius: var(--ddd-radius-sm, 4px); + border: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-slateGray, #666); + background: var(--ddd-theme-default-white, white); + color: var(--ddd-theme-default-coalyGray, #222); + font-family: var(--ddd-font-primary, sans-serif); + box-sizing: border-box; + } + :host([dark]) .sort-select, + body.dark-mode .sort-select { + background: var(--ddd-theme-default-coalyGray, #333); + color: var(--ddd-theme-default-white, white); + border-color: var(--ddd-theme-default-slateGray, #666); + } + .sort-select:focus { + outline: var(--ddd-border-md, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + outline-offset: var(--ddd-spacing-1, 2px); + } + .reset-button { + margin-top: var(--ddd-spacing-1, 4px); + background: var(--ddd-theme-default-original87Pink, #e4007c); + border: var(--ddd-border-xs, 1px solid) transparent; + color: var(--ddd-theme-default-white, white); + border-radius: var(--ddd-radius-sm, 4px); + font-size: var(--ddd-font-size-3xs, 11px); + font-family: var(--ddd-font-primary, sans-serif); + font-weight: var(--ddd-font-weight-medium, 500); + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + display: flex; + align-items: center; + justify-content: center; + gap: var(--ddd-spacing-1, 4px); + box-shadow: var(--ddd-boxShadow-sm); + cursor: pointer; + transition: all 0.2s ease; + min-height: var(--ddd-spacing-7, 28px); + } + .reset-button:hover, + .reset-button:focus { + background: var(--ddd-theme-default-beaver70, #c85c2c); + transform: translateY(-1px); + } + .reset-button:focus { + outline: var(--ddd-border-md, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + outline-offset: var(--ddd-spacing-1, 2px); + } + :host([dark]) .reset-button, + body.dark-mode .reset-button { + background: var(--ddd-theme-default-beaver70, #c85c2c); + } + :host([dark]) .reset-button:hover, + :host([dark]) .reset-button:focus, + body.dark-mode .reset-button:hover, + body.dark-mode .reset-button:focus { + background: var(--ddd-theme-default-original87Pink, #e4007c); + } + .collapseFilter { + display: none; + } + + /* Visually hidden content for screen readers */ + .visually-hidden { + position: absolute !important; + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; + } + + /* Loading and fallback messages */ + .loading-message, + .no-results { + grid-column: 1 / -1; + text-align: center; + padding: var(--ddd-spacing-8, 32px); + font-size: var(--ddd-font-size-s, 16px); + color: var(--ddd-theme-default-slateGray, #666); + font-family: var(--ddd-font-primary, sans-serif); + } + :host([dark]) .loading-message, + :host([dark]) .no-results, + body.dark-mode .loading-message, + body.dark-mode .no-results { + color: var(--ddd-theme-default-limestoneGray, #ccc); + } + .fallback-message { + grid-column: 1 / -1; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--ddd-spacing-4, 16px); + } + .fallback-message .no-results { + padding: var(--ddd-spacing-4, 16px) 0; + } + .fallback-message app-hax-use-case { + max-width: 300px; + } + + @media (max-width: 780px) { + .contentSection { + display: block; + } + .leftSection { + width: 100%; + max-width: 100%; + margin-bottom: var(--ddd-spacing-4, 16px); + position: relative; + } + .rightSection { + width: 100%; + } + :host([show-filter]) .filter { + display: flex; + width: 250px; + max-width: 20vw; + } + :host .collapseFilter { + display: flex; + } + h4, + .returnTo h4, + .startNew h4 { + font-size: var(--ddd-font-size-m, 20px); + } + .template-results { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: var(--ddd-spacing-3, 12px); + } + } + + @media (max-width: 600px) { + .contentSection { + display: block; + margin: 0 var(--ddd-spacing-2, 8px); + padding-right: var(--ddd-spacing-2, 8px); + } + .leftSection { + width: 100%; + max-width: 100%; + margin-bottom: var(--ddd-spacing-3, 12px); + position: relative; + } + .rightSection { + width: 100%; + } + :host([show-filter]) .filter { + display: flex; + width: 200px; + max-width: 20vw; + } + :host .collapseFilter { + display: flex; + } + h4, + .returnTo h4, + .startNew h4 { + font-size: var(--ddd-font-size-s, 18px); + } + .template-results { + grid-template-columns: 1fr; + gap: var(--ddd-spacing-2, 8px); + } + } + + @media (max-width: 480px) { + .contentSection { + margin: 0 var(--ddd-spacing-1, 4px); + padding-right: var(--ddd-spacing-1, 4px); + } + h4, + .returnTo h4, + .startNew h4 { + font-size: var(--ddd-font-size-s, 16px); + margin: 0 0 var(--ddd-spacing-3, 12px) 0; + } + .template-results { + grid-template-columns: 1fr; + gap: var(--ddd-spacing-2, 8px); + } + #returnToSection app-hax-search-results { + min-width: 100%; + } + } + .no-results { + font-size: var(--ddd-font-size-s, 16px); + color: light-dark( + var(--ddd-theme-default-coalyGray, #222), + var(--ddd-theme-default-white, white) + ); + } + `, + ]; + } + + testKeydown(e) { + if (e.key === "Escape" || e.key === "Enter") { + this.toggleSearch(); + } + } + + toggleFilterVisibility() { + this.showFilter = !this.showFilter; + } + + handleFilterKeydown(e, filter) { + // Handle keyboard interaction for filter labels (Space and Enter) + if (e.key === " " || e.key === "Enter") { + e.preventDefault(); + this.toggleFilterByButton(filter); + } + } + + render() { + return html` +
+
+
+ +
+ + + + + +
+ Type to search for site templates and existing sites. Press + Escape to clear. +
+
+ +
+ + +
+ +
+ + Filter templates by category + + ${this.filters.map( + (filter, i) => html` + this.toggleFilterByButton(filter)} + aria-describedby="filter-${i}-description" + /> + +
+ Filter to show only ${filter} templates +
+ `, + )} +
+ +
+ Clear all active filters and search terms +
+
+
+ +
+ +
+

Return to...

+
+ + +
+
+ + +
+

Create New Site

+
+
+ ${this.filteredItems.length} templates available +
+ ${this.filteredItems.length > 0 + ? this.filteredItems.map( + (item, index) => html` +
+ this.toggleDisplay(index, e)} + @continue-action=${() => this.continueAction(index)} + > +
+ `, + ) + : this.loading + ? html`

+ Loading templates... +

` + : this.items && this.items.length > 0 + ? html`

+ No templates match your current filters. Reset filters to + see more options. +

` + : html` +
+

+ ${this.errorMessage || + "No templates available. You can still create a blank site."} +

+ + this.toggleDisplay(-1, e)} + @continue-action=${() => this.continueAction(-1)} + > +
+ `} +
+
+
+
+ + + + `; + } + + iconForFilter(filter) { + switch (filter.toLowerCase()) { + case "blog": + return "lrn:write"; + case "brochure": + return "icons:description"; + case "course": + return "hax:lesson"; + case "portfolio": + return "icons:perm-identity"; + case "blank": + return "hax:bricks"; + default: + return "icons:label"; + } + } + toggleFilterByButton(filter) { + if (this.activeFilters.includes(filter)) { + this.activeFilters = this.activeFilters.filter((f) => f !== filter); + } else { + this.activeFilters = [...this.activeFilters, filter]; + } + this.applyFilters(); + this.requestUpdate(); + } + + connectedCallback() { + super.connectedCallback(); + globalThis.addEventListener("jwt-logged-in", this._jwtLoggedIn.bind(this), { + signal: this.windowControllers.signal, + }); + } + + disconnectedCallback() { + this.windowControllers.abort(); + super.disconnectedCallback(); + } + + _jwtLoggedIn(e) { + // When login status changes to true, refresh skeleton list + if (e.detail === true) { + this.isLoggedIn = true; + this.updateSkeletonResults(); + this.updateSiteResults(); + } else if (e.detail === false) { + this.isLoggedIn = false; + } + } + + firstUpdated() { + super.firstUpdated(); + // Skeleton and site results are loaded via autorun watching appReady + isLoggedIn + } + + updated(changedProperties) { + if ( + changedProperties.has("searchQuery") || + changedProperties.has("activeFilters") || + changedProperties.has("items") + ) { + this.applyFilters(); + } + } + + toggleSearch() { + if (!this.disabled) { + this.shadowRoot.querySelector("#searchField").value = ""; + store.appEl.playSound("click"); + this.showSearch = !this.showSearch; + setTimeout(() => { + this.shadowRoot.querySelector("#searchField").focus(); + }, 300); + } + } + + toggleSelection(index) { + if (this.activeUseCase === index) { + this.activeUseCase = false; // Deselect if the same card is clicked + } else { + this.activeUseCase = index; // Select the new card + } + this.requestUpdate(); + } + + handleSearch(event) { + const searchTerm = event.target.value.toLowerCase(); + this.searchTerm = searchTerm; + // keep store in sync for other consumers like app-hax-search-results UI + store.searchTerm = searchTerm; + // delegate actual filtering to applyFilters so search + filters stay in sync + this.applyFilters(); + } + + handleSortChange(e) { + const value = e && e.target && e.target.value ? e.target.value : "az"; + this.sortOption = value; + this.requestUpdate(); + } + + toggleFilter(event) { + const filterValue = event.target.value; + + if (this.activeFilters.includes(filterValue)) { + this.activeFilters = [ + ...this.activeFilters.filter((f) => f !== filterValue), + ]; + } else { + this.activeFilters = [...this.activeFilters, filterValue]; + } + this.applyFilters(); + } + + applyFilters() { + const lowerCaseQuery = this.searchTerm.toLowerCase(); + + // Filter skeletons and blank themes (from this.items) + this.filteredItems = [ + ...this.items.filter((item) => { + if (item.dataType !== "skeleton" && item.dataType !== "blank") + return false; + const matchesSearch = + lowerCaseQuery === "" || + item.useCaseTitle.toLowerCase().includes(lowerCaseQuery) || + (item.useCaseTag && + item.useCaseTag.some((tag) => + tag.toLowerCase().includes(lowerCaseQuery), + )); + + const matchesFilters = + this.activeFilters.length === 0 || + (item.useCaseTag && + this.activeFilters.some((filter) => + item.useCaseTag.includes(filter), + )); + + return matchesSearch && matchesFilters; + }), + ]; + // Filter sites (from this.returningSites) + this.filteredSites = [ + ...this.returningSites.filter((item) => { + if (item.dataType !== "site") { + return false; + } + const original = item.originalData || {}; + const metadata = original.metadata || {}; + const siteMeta = metadata.site || {}; + // Prefer explicit site category metadata, but fall back to the + // useCaseTag list (which may be populated from build data) so that + // newly created sites still filter correctly by type. + let siteCategory = + typeof siteMeta.category !== "undefined" && + siteMeta.category !== null + ? siteMeta.category + : item.useCaseTag || []; + const title = (original.title || "").toLowerCase(); + const description = (original.description || "").toLowerCase(); + const author = (original.author || "").toLowerCase(); + const slug = (original.slug || "").toLowerCase(); + const tags = item.useCaseTag || []; + + const matchesSearch = + lowerCaseQuery === "" || + title.indexOf(lowerCaseQuery) !== -1 || + description.indexOf(lowerCaseQuery) !== -1 || + author.indexOf(lowerCaseQuery) !== -1 || + slug.indexOf(lowerCaseQuery) !== -1 || + tags.some((tag) => tag.toLowerCase().indexOf(lowerCaseQuery) !== -1) || + (typeof siteCategory === "string" && + siteCategory.toLowerCase().indexOf(lowerCaseQuery) !== -1) || + (Array.isArray(siteCategory) && + siteCategory.some( + (cat) => + typeof cat === "string" && + cat.toLowerCase().indexOf(lowerCaseQuery) !== -1, + )); + + const matchesFilters = + this.activeFilters.length === 0 || + this.activeFilters.some((filter) => { + if (typeof siteCategory === "string") { + return siteCategory === filter; + } + if (Array.isArray(siteCategory)) { + return siteCategory.indexOf(filter) !== -1; + } + return false; + }); + + return matchesSearch && matchesFilters; + }), + ]; + } + + removeFilter(event) { + const filterToRemove = event.detail; + this.activeFilters = this.activeFilters.filter((f) => f !== filterToRemove); + this.applyFilters(); // Re-filter results + this.requestUpdate(); + } + + resetFilters() { + this.searchTerm = ""; + store.searchTerm = ""; + this.activeFilters = []; + // Show all templates (skeletons and blank themes) and all sites + this.filteredItems = [ + ...this.items.filter( + (item) => item.dataType === "skeleton" || item.dataType === "blank", + ), + ]; + this.filteredSites = [...this.returningSites]; + + // Clear UI elements + this.shadowRoot.querySelector("#searchField").value = ""; + this.shadowRoot + .querySelectorAll('input[type="checkbox"]') + .forEach((cb) => (cb.checked = false)); + + this.requestUpdate(); + } + + updateSkeletonResults() { + this.loading = true; + this.errorMessage = ""; + + // Require configured endpoint + if (!store.appSettings || !store.appSettings.skeletonsList) { + this.errorMessage = "Skeletons endpoint not configured"; + this.loading = false; + return; + } + + // Build promises: backend call for skeletons, appSettings themes or fallback fetch + const skeletonsPromise = + store.AppHaxAPI && store.AppHaxAPI.makeCall + ? store.AppHaxAPI.makeCall("skeletonsList") + : Promise.reject(new Error("API not available")); + + // Prefer themes from appSettings (injected by backend); fallback to static file + let themesPromise; + if ( + store.appSettings && + store.appSettings.themes && + Object.keys(store.appSettings.themes).length > 0 + ) { + // Use themes from appSettings if available and not empty + themesPromise = Promise.resolve(store.appSettings.themes); + } else { + // Fallback to loading themes.json from static file + const themesUrl = new URL( + "../../../haxcms-elements/lib/themes.json", + import.meta.url, + ).href; + themesPromise = fetch(themesUrl) + .then((response) => { + if (!response.ok) + throw new Error(`Failed Themes (${response.status})`); + return response.json(); + }) + .catch((error) => { + console.warn( + "Failed to load themes.json, using minimal fallback:", + error, + ); + // Return minimal fallback with just clean-one theme + return { + "clean-one": { + element: "clean-one", + name: "Clean One", + description: "A clean, simple theme", + category: ["Blank"], + hidden: false, + }, + }; + }); + } + + Promise.allSettled([skeletonsPromise, themesPromise]) + .then(([skeletonsData, themesData]) => { + // Process skeletons data (expects { status, data: [] }) + const skeletonArray = + skeletonsData.value && + skeletonsData.value.data && + Array.isArray(skeletonsData.value.data) + ? skeletonsData.value.data + : []; + const skeletonItems = + skeletonArray.map((item) => { + let tags = []; + if (Array.isArray(item.category)) { + tags = item.category.filter( + (c) => typeof c === "string" && c.trim() !== "", + ); + } else if ( + typeof item.category === "string" && + item.category.trim() !== "" + ) { + tags = [item.category.trim()]; + } + if (tags.length === 0) tags = ["Empty"]; + tags.forEach((tag) => this.allFilters.add(tag)); // Add to global Set + + const icons = Array.isArray(item.attributes) + ? item.attributes.map((attr) => ({ + icon: attr.icon || "", + tooltip: attr.tooltip || "", + })) + : []; + let thumbnailPath = item.image || ""; + if (thumbnailPath && thumbnailPath.startsWith("@haxtheweb/")) { + // Navigate from current file to simulate node_modules structure and resolve path + // Current file: elements/app-hax/lib/v2/app-hax-use-case-filter.js + // Need to go up to webcomponents root, then navigate to the package + // In node_modules: @haxtheweb/package-name becomes ../../../@haxtheweb/package-name + const packagePath = "../../../../" + thumbnailPath; + thumbnailPath = new URL(packagePath, import.meta.url).href; + } + return { + dataType: "skeleton", + useCaseTitle: item.title || "Untitled Template", + useCaseImage: thumbnailPath || "", + useCaseDescription: item.description || "", + useCaseIcon: icons, + useCaseTag: tags, + demoLink: item["demo-url"] || "#", + skeletonUrl: item["skeleton-url"] || "", + originalData: item, + }; + }) || []; + + // Process themes data into blank use cases (filter out hidden themes) + const themeSource = themesData.value || {}; + const themeItems = Object.entries(themeSource) + .filter(([, theme]) => !theme.hidden) // Exclude hidden system/debug themes + .map(([themeMachineName, theme]) => { + let tags = []; + if (Array.isArray(theme.category)) { + tags = theme.category.filter( + (c) => typeof c === "string" && c.trim() !== "", + ); + } else if ( + typeof theme.category === "string" && + theme.category.trim() !== "" + ) { + tags = [theme.category.trim()]; + } + if (tags.length === 0) tags = ["Blank"]; + tags.forEach((tag) => this.allFilters.add(tag)); // Add to global Set + + // Simple icon array for blank themes + const icons = [{ icon: "icons:build", tooltip: "Customizable" }]; + + // Resolve thumbnail path using import.meta.url navigation + let thumbnailPath = theme.thumbnail || ""; + if (thumbnailPath && thumbnailPath.startsWith("@haxtheweb/")) { + // Navigate from current file to simulate node_modules structure and resolve path + // Current file: elements/app-hax/lib/v2/app-hax-use-case-filter.js + // Need to go up to webcomponents root, then navigate to the package + // In node_modules: @haxtheweb/package-name becomes ../../../@haxtheweb/package-name + const packagePath = "../../../../" + thumbnailPath; + thumbnailPath = new URL(packagePath, import.meta.url).href; + } + + return { + dataType: "blank", + useCaseTitle: theme.name || "Untitled Theme", + useCaseImage: thumbnailPath || "", + useCaseDescription: + theme.description || "Start with a blank site using this theme", + useCaseIcon: icons, + useCaseTag: tags, + demoLink: `https://playground.hax.cloud/site.html?theme=${themeMachineName}`, + originalData: theme, + }; + }); + // Combine skeleton and theme items + this.items = [...skeletonItems, ...themeItems]; + this.filters = Array.from(this.allFilters).sort(); // Set AFTER all items + + if (this.items.length === 0 && !this.errorMessage) { + this.errorMessage = "No Templates Found"; + } + + this.resetFilters(); + }) + .catch((error) => { + this.errorMessage = `Failed to load data: ${error.message}`; + this.items = []; + this.filters = []; + }) + .finally(() => { + this.loading = false; + }); + } + + updateSiteResults() { + this.loading = true; + this.errorMessage = ""; + + try { + // Use store.manifest data instead of demo JSON + const sitesData = store.manifest; + + if (!sitesData || !sitesData.items) { + throw new Error("No manifest data available"); + } + + const siteItems = Array.isArray(sitesData.items) + ? sitesData.items.map((item) => { + let categorySource = + item.metadata && item.metadata.site + ? item.metadata.site.category + : null; + let tags = []; + if (Array.isArray(categorySource)) { + tags = categorySource.filter( + (c) => typeof c === "string" && c.trim() !== "", + ); + } else if ( + typeof categorySource === "string" && + categorySource.trim() !== "" + ) { + tags = [categorySource.trim()]; + } + + // Incorporate build data (e.g., course, website) into tags when + // available so new sites filter correctly by type + let buildType = null; + if (item.build && item.build.type) { + buildType = item.build.type; + } else if ( + item.metadata && + item.metadata.build && + item.metadata.build.type + ) { + buildType = item.metadata.build.type; + } + if (typeof buildType === "string" && buildType.trim() !== "") { + tags.push(buildType.trim()); + } + + // Normalize, dedupe, and provide a sensible default. If no + // category info exists, assume the site is a generic Website so + // that the Website filter still shows these sites. + tags = [ + ...new Set( + tags.filter( + (c) => typeof c === "string" && c.trim() !== "", + ), + ), + ]; + if (tags.length === 0) tags = ["Website"]; + + tags.forEach((tag) => this.allFilters.add(tag)); // Add to global Set + return { + dataType: "site", + useCaseTag: tags, + originalData: item, + ...item, // this spreads every prop into this area that way it can be filtered correctly + }; + }) + : []; + this.returningSites = [...siteItems]; + this.filters = Array.from(this.allFilters).sort(); // Set AFTER all items + this.filteredSites = [...siteItems]; + + if (siteItems.length === 0 && !this.errorMessage) { + this.errorMessage = "No Sites Found"; + } + + this.requestUpdate(); + this.loading = false; + } catch (error) { + this.errorMessage = `Failed to load data: ${error.message}`; + this.returningSites = []; + this.filteredSites = []; + this.filters = []; + this.loading = false; + } + } + + toggleDisplay(index, event) { + const isSelected = event.detail.isSelected; + + if (this.selectedCardIndex !== null && this.selectedCardIndex !== index) { + // Deselect the previously selected card (only if it's not the fallback) + if ( + this.selectedCardIndex !== -1 && + this.filteredItems[this.selectedCardIndex] + ) { + this.filteredItems[this.selectedCardIndex].isSelected = false; + this.filteredItems[this.selectedCardIndex].showContinue = false; + } + } + + if (isSelected) { + // Select the new card + this.selectedCardIndex = index; + } else { + // Deselect the current card + this.selectedCardIndex = null; + } + + // Update the item state only if it exists (not fallback) + if (index !== -1 && this.filteredItems[index]) { + this.filteredItems[index].isSelected = isSelected; + this.filteredItems[index].showContinue = isSelected; + } + this.requestUpdate(); + } + + async continueAction(index) { + const modal = this.shadowRoot.querySelector("#siteCreationModal"); + + if (modal) { + // Handle fallback case when index is -1 (blank site with clean-one) + if (index === -1) { + modal.title = "Blank Site"; + modal.description = "Create a blank site using the clean-one theme"; + modal.source = ""; + modal.template = "Blank Site"; + modal.themeElement = "clean-one"; + // Generate skeleton data for fallback blank site with Home page + modal.skeletonData = { + meta: { + name: "clean-one", + type: "skeleton", + }, + site: { + name: "clean-one", + theme: "clean-one", + }, + build: { + type: "skeleton", + structure: "from-skeleton", + items: [ + { + id: "item-home-clean-one", + title: "Home", + slug: "home", + order: 0, + parent: null, + indent: 0, + content: + "

Edit this page to get started on your HAX site!

", + metadata: { + published: true, + hideInMenu: false, + tags: [], + }, + }, + ], + files: [], + }, + theme: {}, + }; + // Use the template title as the default site name for the blank site + modal.siteName = modal.title; + modal.openModal(); + return; + } + + const selectedTemplate = this.filteredItems[index]; + if (!selectedTemplate) { + console.warn("No template found at index:", index); + return; + } + + // Set the template details in the modal + modal.title = selectedTemplate.useCaseTitle; + modal.description = selectedTemplate.useCaseDescription; + modal.source = selectedTemplate.useCaseImage; + modal.template = selectedTemplate.useCaseTitle; + + // Handle skeleton templates by loading the skeleton file + if ( + selectedTemplate.dataType === "skeleton" && + selectedTemplate.skeletonUrl + ) { + try { + const rawUrl = selectedTemplate.skeletonUrl || ""; + // Enforce root-relative URL so requests stay on current origin + // and cannot be pointed at arbitrary external hosts. + if (!rawUrl.startsWith("/")) { + console.warn( + "Refusing to load skeleton from non-root-relative URL:", + rawUrl, + ); + return; + } + const response = await fetch(rawUrl); + if (response.ok) { + const skeletonData = await response.json(); + // Store skeleton data for use in site creation + modal.skeletonData = skeletonData.data || skeletonData; + modal.themeElement = + (modal.skeletonData.site && modal.skeletonData.site.theme) || + "clean-one"; + } else { + console.warn(`Failed to load skeleton from ${rawUrl}`); + modal.themeElement = "clean-one"; // fallback + } + } catch (error) { + console.warn("Error loading skeleton:", error); + modal.themeElement = "clean-one"; // fallback + } + } else if ( + selectedTemplate.dataType === "blank" && + selectedTemplate.originalData.element + ) { + // Generate skeleton data for blank themes with a Home page + modal.themeElement = selectedTemplate.originalData.element; + modal.skeletonData = { + meta: { + name: selectedTemplate.originalData.element, + type: "skeleton", + }, + site: { + name: selectedTemplate.originalData.element, + theme: selectedTemplate.originalData.element, + }, + build: { + type: "skeleton", + structure: "from-skeleton", + items: [ + { + id: `item-home-${selectedTemplate.originalData.element}`, + title: "Home", + slug: "home", + order: 0, + parent: null, + indent: 0, + content: + "

Edit this page to get started on your HAX site!

", + metadata: { + published: true, + hideInMenu: false, + tags: [], + }, + }, + ], + files: [], + }, + theme: {}, + }; + } else { + modal.themeElement = "clean-one"; // fallback + } + + // Prepopulate the site name from the selected template's title + modal.siteName = + selectedTemplate.useCaseTitle || modal.title || "New site"; + + // Open the modal + modal.openModal(); + } + } + + handleModalClosed(event) { + // If modal was cancelled (not completed), reset selected states + if (event.detail && event.detail.cancelled) { + // Reset the selected card if one was selected + if ( + this.selectedCardIndex !== null && + this.filteredItems[this.selectedCardIndex] + ) { + this.filteredItems[this.selectedCardIndex].isSelected = false; + this.filteredItems[this.selectedCardIndex].showContinue = false; + this.selectedCardIndex = null; + this.requestUpdate(); + } + } + } +} +customElements.define("app-hax-use-case-filter", AppHaxUseCaseFilter); diff --git a/elements/app-hax/lib/v2/app-hax-use-case.js b/elements/app-hax/lib/v2/app-hax-use-case.js new file mode 100644 index 0000000000..7852c5e955 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-use-case.js @@ -0,0 +1,406 @@ +/* eslint-disable no-return-assign */ +import { LitElement, html, css } from "lit"; +import "@haxtheweb/simple-tooltip/simple-tooltip.js"; +import { store } from "./AppHaxStore.js"; + +export class AppHaxUseCase extends LitElement { + static get tag() { + return "app-hax-use-case"; + } + + constructor() { + super(); + this.title = ""; + this.description = ""; + this.source = ""; + this.demoLink = ""; + this.iconImage = []; + this.isSelected = false; + this.showContinue = false; + } + + static get properties() { + return { + title: { type: String }, + description: { type: String }, + source: { type: String }, + demoLink: { type: String }, + iconImage: { type: Array }, + isSelected: { type: Boolean, reflect: true }, + showContinue: { type: Boolean }, + }; + } + + updated(changedProperties) {} + + static get styles() { + return [ + css` + :host { + display: flex; + flex-direction: column; + text-align: left; + margin: 4px; + font-family: var(--ddd-font-primary); + color: light-dark( + var(--ddd-theme-default-nittanyNavy), + var(--ddd-theme-default-white) + ); + background-color: light-dark( + white, + var(--ddd-theme-default-coalyGray, #222) + ); + border: var(--ddd-border-sm); + border-color: light-dark( + var(--ddd-theme-default-slateGray, #c4c4c4), + var(--ddd-theme-default-slateGray, #666) + ); + box-shadow: light-dark( + 0px 1px 3px rgba(0, 0, 0, 0.1), + 0px 1px 3px rgba(0, 0, 0, 0.2) + ); + border-radius: var(--ddd-radius-sm, 4px); + cursor: pointer; + transition: all 0.2s ease; + } + :host(:hover), + :host(:focus-within) { + transform: translateY(-2px) scale(1.02); + border-color: light-dark( + var(--ddd-theme-default-keystoneYellow, #ffd100), + var(--ddd-theme-default-keystoneYellow, #ffd100) + ); + box-shadow: light-dark( + 4px 8px 24px rgba(28, 28, 28, 0.15), + 4px 8px 24px rgba(0, 0, 0, 0.5) + ); + } + .cardContent { + padding: 8px 12px 16px; + } + .image img { + border-top-right-radius: 6px; + border-top-left-radius: 6px; + border-bottom: solid var(--ddd-theme-default-nittanyNavy) 8px; + overflow: clip; + justify-self: center; + } + .image { + position: relative; + display: inline-block; + } + .icons { + position: absolute; + bottom: 14px; + left: 8px; + display: flex; + gap: 4px; + z-index: 10; + } + .icon-wrapper { + position: relative; + width: 24px; + height: 24px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + } + .icon-wrapper::before { + content: ""; + position: absolute; + width: 100%; + height: 100%; + background-color: white; + border-radius: 50%; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + .tooltip-container { + display: none; + flex-direction: column; + position: absolute; + top: 32px; + left: 0; /* Align with first icon */ + background-color: white; + color: black; + padding: 8px; + border-radius: 6px; + box-shadow: rgba(0, 0, 0, 0.2) 0px 4px 6px; + width: max-content; + z-index: 20; + } + .tooltip { + font-size: 12px; + padding: 4px 8px; + border-bottom: 1px solid #ccc; + text-align: left; + white-space: nowrap; + } + .tooltip:last-child { + border-bottom: none; + } + .icons:hover .tooltip-container { + display: block; + } + .tooltip-row { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 8px; + border-bottom: 1px solid #ccc; + } + + .tooltip-row:last-child { + border-bottom: none; + } + + .tooltip-icon { + width: 20px; + height: 20px; + } + h3 { + font-size: var(--ddd-font-size-4xs); + } + p { + font-size: var(--ddd-font-size-4xs); + padding: 0; + margin: 0; + } + a:link { + color: var(--ddd-theme-default-nittanyNavy, #001e44); + text-decoration: underline; + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-3xs, 11px); + font-weight: var(--ddd-font-weight-medium, 500); + transition: color 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + } + + a:visited { + color: var(--ddd-theme-default-slateGray, #666); + } + + a:hover, + a:focus { + color: var(--ddd-theme-default-keystoneYellow, #ffd100); + text-decoration: none; + } + simple-icon-lite { + color: var(--ddd-theme-default-nittanyNavy, #001e44); + --simple-icon-width: var(--ddd-icon-4xs, 20px); + --simple-icon-height: var(--ddd-icon-4xs, 20px); + } + button { + display: flex; + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + border: var(--ddd-border-xs, 1px solid) transparent; + border-radius: var(--ddd-radius-sm, 4px); + font-family: var(--ddd-font-primary, sans-serif); + font-size: var(--ddd-font-size-3xs, 11px); + font-weight: var(--ddd-font-weight-medium, 500); + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + margin: 0px var(--ddd-spacing-1, 4px); + min-height: var(--ddd-spacing-7, 28px); + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: var(--ddd-boxShadow-sm); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + button:focus, + button:hover { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + transform: translateY(-1px); + box-shadow: var(--ddd-boxShadow-md); + } + .cardBottom { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 6px; + padding: 0px 12px 16px 12px; + gap: 4px; + } + + .cardBottom button, + .cardBottom a { + flex: 1; + margin: 0 2px; + min-width: 0; + font-size: var(--ddd-font-size-3xs, 11px); + } + + :host([isSelected]) button.select { + background-color: var(--ddd-theme-default-nittanyNavy); + } + .titleBar { + display: inline-flex; + flex-direction: column; + text-align: left; + padding: 0px var(--ddd-spacing-3, 12px); + } + .titleBar h3 { + margin: 0; + font-size: var(--ddd-font-size-4xs, 14px); + height: var(--ddd-spacing-6, 24px); + overflow: hidden; + } + .titleBar p { + font-size: var(--ddd-font-size-4xs, 12px); + line-height: 1.4; + height: var(--ddd-spacing-12, 48px); + overflow: hidden; + } + @media (max-width: 768px) { + :host { + margin: var(--ddd-spacing-1, 4px); + min-height: 200px; + width: 100%; + max-width: none; + } + .image img { + width: 100%; + max-width: none; + } + .cardContent { + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px) + var(--ddd-spacing-4, 16px); + } + .titleBar { + padding: 0px var(--ddd-spacing-3, 12px); + } + .cardBottom { + gap: var(--ddd-spacing-2, 8px); + padding: 0px var(--ddd-spacing-3, 12px) var(--ddd-spacing-4, 16px) + var(--ddd-spacing-3, 12px); + } + .cardBottom button, + .cardBottom a { + font-size: var(--ddd-font-size-3xs, 12px); + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + min-height: var(--ddd-spacing-8, 32px); + margin: 0; + } + h3 { + font-size: var(--ddd-font-size-s, 16px) !important; + margin: var(--ddd-spacing-2, 8px) 0; + } + p { + font-size: var(--ddd-font-size-xs, 14px); + line-height: 1.4; + } + } + + @media (min-width: 769px) { + :host, + .image img { + display: flex; + width: 220px; + } + :host .collapseFilter { + display: flex; + } + } + `, + ]; + } + + toggleDisplay() { + this.isSelected = !this.isSelected; + this.showContinue = this.isSelected; + + this.dispatchEvent( + new CustomEvent("toggle-display", { + detail: { isSelected: this.isSelected }, + bubbles: true, + composed: true, + }), + ); + + // If selected, immediately trigger the continue action to open modal + if (this.isSelected) { + setTimeout(() => { + this.continueAction(); + }, 100); // Small delay to allow state to update + } + } + + continueAction() { + this.dispatchEvent( + new CustomEvent("continue-action", { + detail: { + title: this.title, + description: this.description, + source: this.source, + template: this.title, // Using title as template identifier + }, + bubbles: true, + composed: true, + }), + ); + } + + openDemo() { + if (this.demoLink) { + globalThis.open(this.demoLink, "_blank"); + } + } + + render() { + return html` +
+
+ ${this.title} +
+ ${this.iconImage.map( + (icon) => html` +
+ +
+ `, + )} +
+ ${this.iconImage.map( + (icon) => html` +
+ +
${icon.tooltip}
+
+ `, + )} +
+
+
+
+

${this.title}

+

${this.description}

+
+
+ + +
+
+ `; + } +} +customElements.define(AppHaxUseCase.tag, AppHaxUseCase); diff --git a/elements/app-hax/lib/v2/app-hax-user-access-modal.js b/elements/app-hax/lib/v2/app-hax-user-access-modal.js new file mode 100644 index 0000000000..0b7e76dc74 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-user-access-modal.js @@ -0,0 +1,502 @@ +/** + * Copyright 2025 The Pennsylvania State University + * @license Apache-2.0, see License.md for full text. + */ +import { html, css } from "lit"; +import { DDD } from "@haxtheweb/d-d-d/d-d-d.js"; +import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import "@haxtheweb/rpg-character/rpg-character.js"; +import "@haxtheweb/simple-icon/lib/simple-icons.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-button.js"; +import { store } from "./AppHaxStore.js"; + +/** + * `app-hax-user-access-modal` + * `Modal for managing user access to HAXiam sites` + * + * @demo demo/index.html + * @element app-hax-user-access-modal + */ +class AppHaxUserAccessModal extends I18NMixin(DDD) { + /** + * Convention we use + */ + static get tag() { + return "app-hax-user-access-modal"; + } + + constructor() { + super(); + this.username = ""; + this.loading = false; + this.error = ""; + this.siteTitle = ""; + this.t = { + userAccess: "User Access", + enterUsername: "Enter username to grant access", + usernamePlaceholder: "Username", + addUser: "Add User", + cancel: "Cancel", + userAccessGranted: "User access granted successfully!", + userNotFound: "User not found or unauthorized", + loadingAddingUser: "Adding user...", + grantAccessTo: "Grant access to:", + }; + } + + static get properties() { + return { + ...super.properties, + /** + * Username to add + */ + username: { + type: String, + }, + /** + * Loading state + */ + loading: { + type: Boolean, + }, + /** + * Error message + */ + error: { + type: String, + }, + /** + * Current site title + */ + siteTitle: { + type: String, + }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + display: block; + font-family: var(--ddd-font-primary); + background-color: var(--ddd-theme-default-white); + border-radius: var(--ddd-radius-sm); + padding: var(--ddd-spacing-6); + min-width: 420px; + max-width: 500px; + } + + .modal-content { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--ddd-spacing-4); + } + + .site-info { + text-align: center; + margin-bottom: var(--ddd-spacing-2); + } + + .site-title { + color: var(--ddd-theme-default-nittanyNavy); + font-weight: var(--ddd-font-weight-bold); + font-size: var(--ddd-font-size-s); + margin: var(--ddd-spacing-1) 0; + } + + .character-container { + display: flex; + justify-content: center; + margin: var(--ddd-spacing-2) 0; + padding: var(--ddd-spacing-3); + border-radius: var(--ddd-radius-sm); + background-color: var(--ddd-theme-default-limestoneLight); + } + + .input-container { + width: 100%; + display: flex; + flex-direction: column; + gap: var(--ddd-spacing-2); + } + + input { + padding: var(--ddd-spacing-3); + border: var(--ddd-border-sm); + border-radius: var(--ddd-radius-xs); + font-family: var(--ddd-font-primary); + font-size: var(--ddd-font-size-s); + width: 100%; + box-sizing: border-box; + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + } + + input:focus { + outline: none; + border-color: var(--ddd-theme-default-nittanyNavy); + box-shadow: 0 0 0 2px var(--ddd-theme-default-potential30); + } + + .buttons { + display: flex; + gap: var(--ddd-spacing-3); + justify-content: center; + width: 100%; + margin-top: var(--ddd-spacing-3); + } + + button { + background: var(--ddd-theme-default-nittanyNavy, #001e44); + color: var(--ddd-theme-default-white, white); + border: none; + border-radius: var(--ddd-radius-sm); + padding: var(--ddd-spacing-3) var(--ddd-spacing-5); + font-family: var(--ddd-font-primary); + font-size: var(--ddd-font-size-s); + font-weight: var(--ddd-font-weight-medium); + cursor: pointer; + transition: all 0.2s ease; + min-width: 100px; + display: flex; + align-items: center; + justify-content: center; + } + + button:hover:not(:disabled) { + background: var(--ddd-theme-default-keystoneYellow, #ffd100); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + transform: translateY(-1px); + box-shadow: var(--ddd-boxShadow-sm); + } + + button:disabled { + background: var(--ddd-theme-default-slateGray); + cursor: not-allowed; + transform: none; + box-shadow: none; + opacity: 0.6; + } + + .cancel-button { + background: transparent; + color: var(--ddd-theme-default-nittanyNavy); + border: var(--ddd-border-sm); + } + + .cancel-button:hover:not(:disabled) { + background: var(--ddd-theme-default-slateLight); + color: var(--ddd-theme-default-nittanyNavy); + transform: translateY(-1px); + } + + .error { + color: var(--ddd-theme-default-original87Pink); + font-size: var(--ddd-font-size-xs); + text-align: center; + background-color: var(--ddd-theme-default-original87Pink10); + padding: var(--ddd-spacing-2); + border-radius: var(--ddd-radius-xs); + border: 1px solid var(--ddd-theme-default-original87Pink30); + } + + .loading { + display: flex; + align-items: center; + gap: var(--ddd-spacing-2); + justify-content: center; + } + + .loading simple-icon { + --simple-icon-width: 16px; + --simple-icon-height: 16px; + animation: spin 1s linear infinite; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + + h3 { + margin: 0; + color: var(--ddd-theme-default-nittanyNavy); + font-size: var(--ddd-font-size-l); + text-align: center; + font-weight: var(--ddd-font-weight-bold); + } + + p { + margin: 0; + color: var(--ddd-theme-default-coalyGray); + font-size: var(--ddd-font-size-s); + text-align: center; + line-height: 1.4; + } + + .empty-character { + display: flex; + align-items: center; + justify-content: center; + width: 120px; + height: 120px; + color: var(--ddd-theme-default-slateGray); + font-size: var(--ddd-font-size-xs); + text-align: center; + } + `, + ]; + } + + render() { + return html` +
+

${this.t.userAccess}

+ + ${this.siteTitle + ? html` +
+

${this.t.grantAccessTo}

+
${this.siteTitle}
+
+ ` + : ""} + +

${this.t.enterUsername}

+ +
+ ${this.username + ? html` + + ` + : html` +
+ Character will appear when you enter a username +
+ `} +
+ +
+ + + ${this.error ? html`
${this.error}
` : ""} +
+ +
+ + + +
+
+ `; + } + + /** + * Handle username input changes + */ + _handleUsernameInput(e) { + this.username = e.target.value; + // Clear error when user types + if (this.error) { + this.error = ""; + } + } + + /** + * Handle keydown events + */ + _handleKeydown(e) { + if (e.key === "Enter" && this.username.trim() && !this.loading) { + this._handleAddUser(); + } else if (e.key === "Escape") { + this._handleCancel(); + } + } + + /** + * Handle add user button click + */ + async _handleAddUser() { + if (!this.username.trim()) { + return; + } + + this.loading = true; + this.error = ""; + + try { + const response = await this._addUserAccess(this.username.trim()); + + if (response.ok) { + // Play success sound + if (store.appEl && store.appEl.playSound) { + store.appEl.playSound("success"); + } + // Success - show toast and close modal + this._showSuccessToast(); + this._closeModal(); + // Reset form + this.username = ""; + } else if (response.status === 403) { + // User not found or unauthorized + this.error = this.t.userNotFound; + } else { + // Other error + this.error = `Error: ${response.status} ${response.statusText}`; + } + } catch (error) { + console.error("Error adding user access:", error); + this.error = "Network error occurred. Please try again."; + } finally { + this.loading = false; + } + } + + /** + * Handle cancel button click + */ + _handleCancel() { + // Removed sound effects for modal close/cancel as requested + this._closeModal(); + } + + /** + * Add user access via HAXiam API + */ + async _addUserAccess(username) { + // Get the site name from the store - this should be the directory name + let siteName = null; + if (store.activeSite && store.activeSite.name) { + siteName = store.activeSite.name; + } else if ( + store.activeSite && + store.activeSite.metadata && + store.activeSite.metadata.site && + store.activeSite.metadata.site.name + ) { + siteName = store.activeSite.metadata.site.name; + } + + if (!siteName) { + throw new Error("Unable to determine site name"); + } + + // Use the secure AppHaxAPI.makeCall method with proper token validation + const response = await store.AppHaxAPI.makeCall("haxiamAddUserAccess", { + userName: username, + siteName: siteName, + }); + + // Convert to fetch-like response object for compatibility with existing error handling + return { + ok: !response.__failed, + status: response.__failed ? response.__failed.status : 200, + statusText: response.__failed ? response.__failed.message : "OK", + data: response, + }; + } + + /** + * Show success toast with RPG character matching the added user + */ + _showSuccessToast() { + // Use the existing toast system but with the character seed matching the added user + store.toast(this.t.userAccessGranted, 4000, { + hat: "construction", + userName: this.username, // This ensures the toast character matches the user we just added + }); + } + + /** + * Close the modal + */ + _closeModal() { + // Restore body scrolling + document.body.style.overflow = ""; + + globalThis.dispatchEvent( + new CustomEvent("simple-modal-hide", { + bubbles: true, + composed: true, + cancelable: true, + detail: {}, + }), + ); + } + + /** + * Focus input when modal opens + */ + firstUpdated() { + super.firstUpdated(); + // Set site title from store if available + if (store.activeSite && store.activeSite.title) { + this.siteTitle = store.activeSite.title; + } + + // Focus input after a brief delay + setTimeout(() => { + const input = this.shadowRoot.querySelector("input"); + if (input) { + input.focus(); + } + }, 100); + } + + /** + * Reset form when modal opens + */ + connectedCallback() { + super.connectedCallback(); + this.username = ""; + this.error = ""; + this.loading = false; + + // Prevent body scrolling when modal is connected/opened + document.body.style.overflow = "hidden"; + } +} + +globalThis.customElements.define( + AppHaxUserAccessModal.tag, + AppHaxUserAccessModal, +); +export { AppHaxUserAccessModal }; diff --git a/elements/app-hax/lib/v2/app-hax-user-menu-button.js b/elements/app-hax/lib/v2/app-hax-user-menu-button.js new file mode 100644 index 0000000000..301fa174c9 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-user-menu-button.js @@ -0,0 +1,129 @@ +// TODO: Text-overflow-ellipses + +// dependencies / things imported +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; + +export class AppHaxUserMenuButton extends DDDSuper(LitElement) { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-user-menu-button"; + } + + constructor() { + super(); + this.icon = "account-circle"; + this.label = "Default"; + } + + handleClick(e) { + // Find the parent anchor element and trigger its click + const parentAnchor = this.parentElement; + if (parentAnchor && parentAnchor.tagName.toLowerCase() === "a") { + e.stopPropagation(); + parentAnchor.click(); + } + } + + static get properties() { + return { + icon: { type: String }, + label: { type: String }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + font-family: var(--ddd-font-primary, sans-serif); + text-align: center; + width: 100%; + display: block; + } + + .menu-button { + display: flex; + align-items: center; + width: 100%; + border: none; + margin: 0; + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + font-size: var(--ddd-font-size-3xs, 12px); + text-align: left; + color: var(--ddd-theme-default-nittanyNavy, #001e44); + background: transparent; + cursor: pointer; + font-family: var(--ddd-font-primary, sans-serif); + transition: all 0.2s ease; + min-height: var(--ddd-spacing-8, 32px); + box-sizing: border-box; + } + + :host([dark]) .menu-button, + body.dark-mode .menu-button { + color: var(--ddd-theme-default-white, white); + } + + .menu-button:hover, + .menu-button:active, + .menu-button:focus { + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + outline: none; + } + + :host([dark]) .menu-button:hover, + :host([dark]) .menu-button:active, + :host([dark]) .menu-button:focus, + body.dark-mode .menu-button:hover, + body.dark-mode .menu-button:active, + body.dark-mode .menu-button:focus { + background: var(--ddd-theme-default-slateGray, #666); + color: var(--ddd-theme-default-white, white); + } + + :host(.logout) .menu-button:hover, + :host(.logout) .menu-button:active, + :host(.logout) .menu-button:focus { + background: var(--ddd-theme-default-original87Pink, #e4007c); + color: var(--ddd-theme-default-white, white); + } + + .icon { + padding-right: var(--ddd-spacing-2, 8px); + font-size: var(--ddd-font-size-xs, 14px); + flex-shrink: 0; + display: flex; + align-items: center; + } + + .label { + flex: 1; + text-align: left; + } + `, + ]; + } + + render() { + return html` + + `; + } +} +customElements.define(AppHaxUserMenuButton.tag, AppHaxUserMenuButton); diff --git a/elements/app-hax/lib/v2/app-hax-user-menu.js b/elements/app-hax/lib/v2/app-hax-user-menu.js new file mode 100644 index 0000000000..f2f8b3e6d5 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-user-menu.js @@ -0,0 +1,338 @@ +// TODO: Create app-hax-user-menu-button to be tossed into this +// TODO: Create prefix and suffix sections for sound/light toggles and other shtuff + +// dependencies / things imported +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; + +export class AppHaxUserMenu extends DDDSuper(LitElement) { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-user-menu"; + } + + constructor() { + super(); + this.isOpen = false; + this.icon = "account-circle"; + this.addEventListener("keydown", this._handleKeydown.bind(this)); + } + + static get properties() { + return { + isOpen: { type: Boolean, reflect: true, attribute: "is-open" }, + icon: { type: String, reflect: true }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + font-family: var(--ddd-font-primary, sans-serif); + text-align: center; + display: inline-block; + margin: 0px; + padding: 0px; + position: relative; + } + + .entireComponent { + max-height: var(--ddd-spacing-10, 40px); + } + + .menuToggle { + cursor: pointer; + max-height: var(--ddd-spacing-10, 40px); + } + + .user-menu { + display: none; + } + + .user-menu.open { + display: block; + top: var(--ddd-spacing-12, 48px); + right: 0px; + position: absolute; + border: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-slateGray, #666); + background: var(--ddd-theme-default-white, white); + border-radius: var(--ddd-radius-sm, 4px); + box-shadow: var(--ddd-boxShadow-lg); + min-width: var(--ddd-spacing-30, 200px); + z-index: 1000; + overflow: hidden; + } + + :host([dark]) .user-menu.open, + body.dark-mode .user-menu.open { + background: var(--ddd-theme-default-coalyGray, #333); + border-color: var(--ddd-theme-default-slateGray, #666); + } + + .user-menu.open ::slotted(*) { + display: block; + width: 100%; + margin: 0; + font-size: var(--ddd-font-size-3xs, 12px); + text-align: left; + font-family: var(--ddd-font-primary, sans-serif); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + background: transparent; + text-decoration: none; + } + + :host([dark]) .user-menu.open ::slotted(*), + body.dark-mode .user-menu.open ::slotted(*) { + color: var(--ddd-theme-default-white, white); + } + + .user-menu.open .main-menu ::slotted(*:hover), + .user-menu.open .main-menu ::slotted(*:active), + .user-menu.open .main-menu ::slotted(*:focus) { + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + } + + :host([dark]) .user-menu.open .main-menu ::slotted(*:hover), + :host([dark]) .user-menu.open .main-menu ::slotted(*:active), + :host([dark]) .user-menu.open .main-menu ::slotted(*:focus), + body.dark-mode .user-menu.open .main-menu ::slotted(*:hover), + body.dark-mode .user-menu.open .main-menu ::slotted(*:active), + body.dark-mode .user-menu.open .main-menu ::slotted(*:focus) { + background: var(--ddd-theme-default-slateGray, #666); + color: var(--ddd-theme-default-white, white); + } + + .user-menu.open .post-menu ::slotted(*:hover), + .user-menu.open .post-menu ::slotted(*:active), + .user-menu.open .post-menu ::slotted(*:focus) { + background: var(--ddd-theme-default-original87Pink, #e4007c); + color: var(--ddd-theme-default-white, white); + } + + .user-menu ::slotted(button) { + cursor: pointer; + } + + .user-menu ::slotted(*) simple-icon-lite { + padding-right: var(--ddd-spacing-2, 8px); + } + + .pre-menu, + .post-menu { + border-top: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-limestoneGray, #f5f5f5); + } + + .pre-menu:first-child, + .main-menu:first-child { + border-top: none; + } + + /* Keyboard focus indicators */ + .user-menu ::slotted(*:focus), + .user-menu ::slotted(*[tabindex="0"]:focus) { + outline: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + outline-offset: -2px; + } + `, + ]; + } + + render() { + return html` +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+
+ `; + } + + /** + * Handle keyboard navigation for menu toggle + */ + _handleMenuToggleKeydown(e) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + this._toggleMenu(); + } else if (e.key === "Escape" && this.isOpen) { + e.preventDefault(); + this._closeMenu(); + } + } + + /** + * Handle keyboard navigation within menu + */ + _handleKeydown(e) { + if (!this.isOpen) return; + + const menuItems = this._getMenuItems(); + const currentIndex = this._getCurrentMenuItemIndex(menuItems); + + switch (e.key) { + case "Escape": + e.preventDefault(); + this._closeMenu(); + this._focusToggle(); + break; + case "ArrowDown": + e.preventDefault(); + this._focusNextItem(menuItems, currentIndex); + break; + case "ArrowUp": + e.preventDefault(); + this._focusPreviousItem(menuItems, currentIndex); + break; + case "Home": + e.preventDefault(); + this._focusFirstItem(menuItems); + break; + case "End": + e.preventDefault(); + this._focusLastItem(menuItems); + break; + } + } + + /** + * Toggle menu open/closed state + */ + _toggleMenu() { + this.isOpen = !this.isOpen; + if (this.isOpen) { + // Focus first menu item when opening + setTimeout(() => { + const menuItems = this._getMenuItems(); + if (menuItems.length > 0) { + menuItems[0].focus(); + } + }, 0); + } + } + + /** + * Close menu and restore focus + */ + _closeMenu() { + this.isOpen = false; + } + + /** + * Focus the menu toggle button + */ + _focusToggle() { + const toggle = this.shadowRoot.querySelector(".menuToggle"); + if (toggle) { + toggle.focus(); + } + } + + /** + * Get all focusable menu items + */ + _getMenuItems() { + const menu = this.shadowRoot.querySelector(".user-menu"); + if (!menu) return []; + + const items = menu.querySelectorAll("slot"); + const menuItems = []; + + items.forEach((slot) => { + const assignedElements = slot.assignedElements(); + assignedElements.forEach((el) => { + // Find focusable elements within slotted content + const focusable = el.matches('a, button, [tabindex="0"]') + ? [el] + : el.querySelectorAll('a, button, [tabindex="0"]'); + menuItems.push(...focusable); + }); + }); + + return menuItems; + } + + /** + * Get current focused menu item index + */ + _getCurrentMenuItemIndex(menuItems) { + const activeElement = + this.shadowRoot.activeElement || document.activeElement; + return menuItems.indexOf(activeElement); + } + + /** + * Focus next menu item + */ + _focusNextItem(menuItems, currentIndex) { + const nextIndex = + currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0; + if (menuItems[nextIndex]) { + menuItems[nextIndex].focus(); + } + } + + /** + * Focus previous menu item + */ + _focusPreviousItem(menuItems, currentIndex) { + const prevIndex = + currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1; + if (menuItems[prevIndex]) { + menuItems[prevIndex].focus(); + } + } + + /** + * Focus first menu item + */ + _focusFirstItem(menuItems) { + if (menuItems[0]) { + menuItems[0].focus(); + } + } + + /** + * Focus last menu item + */ + _focusLastItem(menuItems) { + if (menuItems[menuItems.length - 1]) { + menuItems[menuItems.length - 1].focus(); + } + } +} +customElements.define(AppHaxUserMenu.tag, AppHaxUserMenu); diff --git a/elements/app-hax/lib/v2/app-hax-wired-toggle.js b/elements/app-hax/lib/v2/app-hax-wired-toggle.js new file mode 100644 index 0000000000..16ebeb6b57 --- /dev/null +++ b/elements/app-hax/lib/v2/app-hax-wired-toggle.js @@ -0,0 +1,99 @@ +import { autorun, toJS } from "mobx"; +import { html, css } from "lit"; +import { store } from "./AppHaxStore.js"; +import { WiredDarkmodeToggle } from "@haxtheweb/haxcms-elements/lib/core/ui/wired-darkmode-toggle/wired-darkmode-toggle.js"; +import { SimpleTourFinder } from "@haxtheweb/simple-popover/lib/SimpleTourFinder.js"; + +export class AppHAXWiredToggle extends SimpleTourFinder(WiredDarkmodeToggle) { + constructor() { + super(); + this.tourName = "hax"; + // Create a media query to monitor platform color scheme changes + this.darkModeMediaQuery = globalThis.matchMedia( + "(prefers-color-scheme: dark)", + ); + + // Function to handle both autorun updates and media query changes + this._updateToggleState = () => { + this.checked = toJS(store.darkMode); + // Disable toggle when platform is in dark mode, preventing switch to light mode + if (this.darkModeMediaQuery.matches) { + this.disabled = true; + } else { + this.disabled = false; + } + }; + + // Set up autorun for store changes + autorun(this._updateToggleState); + + // Listen for platform color scheme changes + this.darkModeMediaQuery.addEventListener("change", this._updateToggleState); + } + + static get tag() { + return "app-hax-wired-toggle"; + } + + disconnectedCallback() { + super.disconnectedCallback(); + // Clean up media query event listener + if (this.darkModeMediaQuery && this._updateToggleState) { + this.darkModeMediaQuery.removeEventListener( + "change", + this._updateToggleState, + ); + } + } + + updated(changedProperties) { + if (super.updated) { + super.updated(changedProperties); + } + changedProperties.forEach((oldValue, propName) => { + if (propName === "checked" && oldValue !== undefined) { + store.darkMode = this[propName]; + } + }); + } + + static get styles() { + return [ + super.styles, + css` + /* Screen reader only text */ + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + `, + ]; + } + render() { + return html` +
+ ${super.render()} +
+ Toggle between light and dark mode themes +
+
+ You can toggle your user interface between "light" and "dark" for your + viewing enjoyment. +
+
+ `; + } +} +customElements.define(AppHAXWiredToggle.tag, AppHAXWiredToggle); diff --git a/elements/app-hax/lib/v2/skeleton-loader.js b/elements/app-hax/lib/v2/skeleton-loader.js new file mode 100644 index 0000000000..4a64dd5766 --- /dev/null +++ b/elements/app-hax/lib/v2/skeleton-loader.js @@ -0,0 +1,191 @@ +/** + * Skeleton Loader Utility + * + * Handles loading and processing skeleton files for the app-hax v2 system. + * Since skeleton files now match the createSite API format directly, + * this utility focuses on loading, validation, and metadata extraction. + */ + +export class SkeletonLoader { + /** + * Load skeleton from JSON data and prepare for app-hax usage + * @param {Object} skeletonData - Raw skeleton JSON data + * @param {Object} options - Loading options + * @returns {Object} Processed skeleton ready for app-hax + */ + static loadSkeleton(skeletonData, options = {}) { + // Validate skeleton format + if (!this.isValidSkeleton(skeletonData)) { + throw new Error("Invalid skeleton format"); + } + + // Extract metadata for app-hax use case display + const useCaseData = { + dataType: "skeleton", + useCaseTitle: skeletonData.meta.useCaseTitle || skeletonData.meta.name, + useCaseDescription: + skeletonData.meta.useCaseDescription || skeletonData.meta.description, + useCaseImage: + skeletonData.meta.useCaseImage || options.defaultImage || "", + useCaseIcon: options.defaultIcons || [ + { icon: "hax:site", tooltip: "Site Template" }, + ], + useCaseTag: this.extractTags(skeletonData), + demoLink: skeletonData.meta.sourceUrl || "#", + originalData: skeletonData, + + // App-hax specific properties + isSelected: false, + showContinue: false, + }; + + return { + // Use case display data + ...useCaseData, + + // Direct createSite API data (no transformation needed) + createSiteData: { + site: skeletonData.site, + build: skeletonData.build, + theme: skeletonData.theme, + }, + }; + } + + /** + * Extract tags for filtering from skeleton + * @param {Object} skeleton - Skeleton data + * @returns {Array} Tags array + */ + static extractTags(skeleton) { + const tags = []; + + // Add categories and tags from meta + if (skeleton.meta.category && Array.isArray(skeleton.meta.category)) { + tags.push(...skeleton.meta.category); + } + if (skeleton.meta.tags && Array.isArray(skeleton.meta.tags)) { + tags.push(...skeleton.meta.tags); + } + + // Add build type as tag + if (skeleton.build && skeleton.build.type) { + tags.push(skeleton.build.type); + } + + // Add theme as tag + if (skeleton.site && skeleton.site.theme) { + tags.push(`theme-${skeleton.site.theme}`); + } + + // Deduplicate and filter empty + return [...new Set(tags.filter((tag) => tag && tag.trim() !== ""))]; + } + + /** + * Validate skeleton format + * @param {Object} skeleton - Skeleton to validate + * @returns {boolean} Is valid skeleton + */ + static isValidSkeleton(skeleton) { + if (!skeleton || typeof skeleton !== "object") return false; + + // Check required top-level structure + if (!skeleton.meta || skeleton.meta.type !== "skeleton") return false; + if (!skeleton.site || !skeleton.build) return false; + + // Check site structure + if (!skeleton.site.name || !skeleton.site.theme) return false; + + // Check build structure + if (!skeleton.build.items || !Array.isArray(skeleton.build.items)) + return false; + + return true; + } + + /** + * Load multiple skeletons from an array of skeleton data + * @param {Array} skeletonArray - Array of skeleton JSON objects + * @param {Object} options - Loading options + * @returns {Array} Array of processed skeletons + */ + static loadSkeletons(skeletonArray, options = {}) { + if (!Array.isArray(skeletonArray)) { + throw new Error("Expected array of skeletons"); + } + + return skeletonArray + .map((skeleton, index) => { + try { + return this.loadSkeleton(skeleton, { + ...options, + index, + }); + } catch (error) { + console.warn(`Failed to load skeleton at index ${index}:`, error); + return null; + } + }) + .filter(Boolean); // Remove failed loads + } + + /** + * Apply user customizations to skeleton createSite data + * @param {Object} skeleton - Processed skeleton from loadSkeleton() + * @param {Object} customizations - User customizations + * @returns {Object} CreateSite data with customizations applied + */ + static applyCustomizations(skeleton, customizations = {}) { + const createSiteData = JSON.parse(JSON.stringify(skeleton.createSiteData)); // Deep clone + + // Apply site customizations + if (customizations.siteName) { + createSiteData.site.name = customizations.siteName; + } + if (customizations.siteDescription) { + createSiteData.site.description = customizations.siteDescription; + } + if (customizations.theme) { + createSiteData.site.theme = customizations.theme; + } + + // Apply theme customizations + if (customizations.color) { + createSiteData.theme.color = customizations.color; + } + if (customizations.icon) { + createSiteData.theme.icon = customizations.icon; + } + + // Apply any additional theme settings + if (customizations.themeSettings) { + Object.assign(createSiteData.theme, customizations.themeSettings); + } + + return createSiteData; + } + + /** + * Generate skeleton metadata for directory processing + * @param {Object} skeleton - Skeleton data + * @returns {Object} Metadata for backend use + */ + static generateSkeletonMetadata(skeleton) { + return { + name: skeleton.meta.name, + title: skeleton.meta.useCaseTitle || skeleton.meta.name, + description: + skeleton.meta.useCaseDescription || skeleton.meta.description, + type: skeleton.build.type || "skeleton", + theme: skeleton.site.theme, + itemCount: skeleton.build.items ? skeleton.build.items.length : 0, + fileCount: skeleton.build.files ? skeleton.build.files.length : 0, + created: skeleton.meta.created, + tags: this.extractTags(skeleton), + sourceUrl: skeleton.meta.sourceUrl || null, + }; + } +} + +export default SkeletonLoader; diff --git a/elements/app-hax/lib/v2/skeleton-uuid-manager.js b/elements/app-hax/lib/v2/skeleton-uuid-manager.js new file mode 100644 index 0000000000..d2f255ee80 --- /dev/null +++ b/elements/app-hax/lib/v2/skeleton-uuid-manager.js @@ -0,0 +1,128 @@ +/** + * UUID Manager for Skeleton Generation + * Handles UUID generation, mapping, and rewriting for skeleton templates + */ + +export class SkeletonUuidManager { + constructor() { + this.uuidMap = new Map(); + this.usedUuids = new Set(); + } + + /** + * Generate a new UUID v4 + * @returns {string} UUID + */ + generateUuid() { + // Simple UUID v4 generation without crypto dependency + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace( + /[xy]/g, + function (c) { + const r = (Math.random() * 16) | 0; + const v = c === "x" ? r : (r & 0x3) | 0x8; + return v.toString(16); + }, + ); + } + + /** + * Get or create UUID for an item + * @param {string} originalId - Original item ID + * @returns {string} UUID + */ + getUuidForItem(originalId) { + if (this.uuidMap.has(originalId)) { + return this.uuidMap.get(originalId); + } + + let newUuid; + do { + newUuid = this.generateUuid(); + } while (this.usedUuids.has(newUuid)); + + this.uuidMap.set(originalId, newUuid); + this.usedUuids.add(newUuid); + return newUuid; + } + + /** + * Create mapping for parent-child relationships + * @param {Array} items - Site items + * @returns {Object} UUID mapping with relationships + */ + createRelationshipMap(items) { + const relationships = {}; + + items.forEach((item) => { + const itemUuid = this.getUuidForItem(item.id); + relationships[itemUuid] = { + originalId: item.id, + uuid: itemUuid, + parentUuid: item.parent ? this.getUuidForItem(item.parent) : null, + children: [], + }; + }); + + // Build children arrays + Object.values(relationships).forEach((item) => { + if (item.parentUuid && relationships[item.parentUuid]) { + relationships[item.parentUuid].children.push(item.uuid); + } + }); + + return relationships; + } + + /** + * Rewrite UUIDs in skeleton data for new site creation + * @param {Object} skeleton - Skeleton data + * @returns {Object} Skeleton with new UUIDs + */ + rewriteSkeletonUuids(skeleton) { + const newManager = new SkeletonUuidManager(); + const oldToNewMap = new Map(); + + // Create new UUIDs for all items + skeleton.structure.forEach((item) => { + if (item.uuid) { + const newUuid = newManager.generateUuid(); + oldToNewMap.set(item.uuid, newUuid); + } + }); + + // Rewrite structure with new UUIDs + const newStructure = skeleton.structure.map((item) => { + const newItem = { ...item }; + + if (item.uuid) { + newItem.uuid = oldToNewMap.get(item.uuid); + } + + if (item.parentUuid && oldToNewMap.has(item.parentUuid)) { + newItem.parentUuid = oldToNewMap.get(item.parentUuid); + } + + return newItem; + }); + + return { + ...skeleton, + structure: newStructure, + meta: { + ...skeleton.meta, + created: new Date().toISOString(), + sourceUuids: "rewritten", + }, + }; + } + + /** + * Reset the manager + */ + reset() { + this.uuidMap.clear(); + this.usedUuids.clear(); + } +} + +export default SkeletonUuidManager; diff --git a/elements/app-hax/lib/wired-darkmode-toggle/images/moonIcon.png b/elements/app-hax/lib/wired-darkmode-toggle/images/moonIcon.png new file mode 100644 index 0000000000..88eb079435 Binary files /dev/null and b/elements/app-hax/lib/wired-darkmode-toggle/images/moonIcon.png differ diff --git a/elements/app-hax/lib/wired-darkmode-toggle/images/sunIcon.png b/elements/app-hax/lib/wired-darkmode-toggle/images/sunIcon.png new file mode 100644 index 0000000000..6b94b10568 Binary files /dev/null and b/elements/app-hax/lib/wired-darkmode-toggle/images/sunIcon.png differ diff --git a/elements/app-hax/lib/wired-darkmode-toggle/wired-darkmode-toggle.js b/elements/app-hax/lib/wired-darkmode-toggle/wired-darkmode-toggle.js index 74b4f72539..d7aa691ce3 100644 --- a/elements/app-hax/lib/wired-darkmode-toggle/wired-darkmode-toggle.js +++ b/elements/app-hax/lib/wired-darkmode-toggle/wired-darkmode-toggle.js @@ -8,14 +8,15 @@ import { WiredToggle } from "wired-elements/lib/wired-toggle.js"; import { html, css, unsafeCSS } from "lit"; // need to highjack in order to alter the scale so we can fit our icon // for states -const sun = new URL("./images/sun.svg", import.meta.url).href; -const moon = new URL("./images/moon.svg", import.meta.url).href; +const sun = new URL("./images/sunIcon.png", import.meta.url).href; +const moon = new URL("./images/moonIcon.png", import.meta.url).href; export class WiredDarkmodeToggle extends WiredToggle { constructor() { super(); this.checked = false; this.label = "Dark mode"; + this.knobFill = svgNode("circle"); } // eslint-disable-next-line class-methods-use-this @@ -28,17 +29,44 @@ export class WiredDarkmodeToggle extends WiredToggle { } draw(svg, size) { - const rect = rectangle(svg, 0, 0, size[0], 48, this.seed); - rect.classList.add("toggle-bar"); + //const rect = rectangle(svg, 0, 0, size[0], 48, this.seed); + //rect.classList.add("toggle-bar"); this.knob = svgNode("g"); this.knob.classList.add("knob"); svg.appendChild(this.knob); - const knobFill = hachureEllipseFill(26, 26, 40, 40, this.seed); - knobFill.classList.add("knobfill"); - this.knob.appendChild(knobFill); + + this.knobFill.setAttribute("cx", 26); + this.knobFill.setAttribute("cy", 26); + this.knobFill.setAttribute("r", 20); + this.knobFill.setAttribute( + "style", + "fill: var(--wired-toggle-off-color); transition: fill 0.3s ease-in-out;", + ); + this.knobFill.classList.add("knobfill"); + this.knob.appendChild(this.knobFill); ellipse(this.knob, 26, 26, 40, 40, this.seed); } + toggleMode(checked) { + if (checked) { + this.knobFill.setAttribute( + "style", + "fill: var(--wired-toggle-on-color);", + ); + } else { + this.knobFill.setAttribute( + "style", + "fill: var(--wired-toggle-off-color);", + ); + } + } + + onChange(event) { + this.checked = event.target.checked; + this.toggleMode(this.checked); + this.dispatchEvent(new Event("change", { bubbles: true, composed: true })); + } + static get properties() { return { checked: { @@ -58,7 +86,7 @@ export class WiredDarkmodeToggle extends WiredToggle { render() { return html`
- + AudioPlayer: audio-player Demo - - +
diff --git a/elements/audio-player/index.html b/elements/audio-player/index.html index d32ffdbf86..2ea52ab23e 100644 --- a/elements/audio-player/index.html +++ b/elements/audio-player/index.html @@ -4,10 +4,10 @@ audio-player documentation - - + + - + diff --git a/elements/audio-player/package.json b/elements/audio-player/package.json index a2376115ca..d466fe1f93 100644 --- a/elements/audio-player/package.json +++ b/elements/audio-player/package.json @@ -44,16 +44,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/video-player": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/audio-player/test/audio-player.test.js b/elements/audio-player/test/audio-player.test.js index 87944f6c5a..9f205ef786 100644 --- a/elements/audio-player/test/audio-player.test.js +++ b/elements/audio-player/test/audio-player.test.js @@ -30,12 +30,14 @@ describe("audio-player test", () => { it("should extend VideoPlayer", () => { expect(element.constructor.name).to.equal("AudioPlayer"); - expect(element).to.be.instanceOf(globalThis.customElements.get('video-player') || Object); + expect(element).to.be.instanceOf( + globalThis.customElements.get("video-player") || Object, + ); }); it("should always return audioOnly as true", () => { expect(element.audioOnly).to.equal(true); - + // Test that it's always true regardless of attempts to change it element.audioOnly = false; expect(element.audioOnly).to.equal(true); @@ -46,9 +48,7 @@ describe("audio-player test", () => { let testElement; beforeEach(async () => { - testElement = await fixture(html` - - `); + testElement = await fixture(html` `); await testElement.updateComplete; }); @@ -84,19 +84,29 @@ describe("audio-player test", () => { it("should handle thumbnailSrc property", async () => { testElement.thumbnailSrc = "https://example.com/thumbnail.jpg"; await testElement.updateComplete; - expect(testElement.thumbnailSrc).to.equal("https://example.com/thumbnail.jpg"); + expect(testElement.thumbnailSrc).to.equal( + "https://example.com/thumbnail.jpg", + ); await expect(testElement).shadowDom.to.be.accessible(); }); it("should handle boolean properties", async () => { const booleanProps = [ - 'learningMode', 'hideYoutubeLink', 'linkable', - 'allowBackgroundPlay', 'darkTranscript', 'disableInteractive', - 'hideTimestamps', 'hideTranscript' + "learningMode", + "hideYoutubeLink", + "linkable", + "allowBackgroundPlay", + "darkTranscript", + "disableInteractive", + "hideTimestamps", + "hideTranscript", ]; for (const prop of booleanProps) { - if (testElement.hasOwnProperty(prop) || prop in testElement.constructor.properties) { + if ( + testElement.hasOwnProperty(prop) || + prop in testElement.constructor.properties + ) { testElement[prop] = true; await testElement.updateComplete; expect(testElement[prop]).to.equal(true); @@ -111,8 +121,8 @@ describe("audio-player test", () => { }); it("should handle crossorigin property", async () => { - const crossOriginValues = ['', 'anonymous', 'use-credentials']; - + const crossOriginValues = ["", "anonymous", "use-credentials"]; + for (const value of crossOriginValues) { testElement.crossorigin = value; await testElement.updateComplete; @@ -136,39 +146,39 @@ describe("audio-player test", () => { `); await testElement.updateComplete; - + expect(testElement.audioOnly).to.be.true; await expect(testElement).shadowDom.to.be.accessible(); }); it("should handle audio sources correctly", async () => { const testElement = await fixture(html` - `); await testElement.updateComplete; - - expect(testElement.source).to.include('.mp3'); - expect(testElement.mediaTitle).to.equal('Audio Test'); + + expect(testElement.source).to.include(".mp3"); + expect(testElement.mediaTitle).to.equal("Audio Test"); await expect(testElement).shadowDom.to.be.accessible(); }); it("should support different audio formats", async () => { const audioFormats = [ - 'https://example.com/audio.mp3', - 'https://example.com/audio.wav', - 'https://example.com/audio.ogg', - 'https://example.com/audio.m4a' + "https://example.com/audio.mp3", + "https://example.com/audio.wav", + "https://example.com/audio.ogg", + "https://example.com/audio.m4a", ]; - + for (const format of audioFormats) { const testElement = await fixture(html` `); await testElement.updateComplete; - + expect(testElement.source).to.equal(format); expect(testElement.audioOnly).to.be.true; await expect(testElement).shadowDom.to.be.accessible(); @@ -249,8 +259,8 @@ describe("audio-player test", () => { it("should handle audio file types correctly", () => { const haxProps = element.constructor.haxProperties; const handles = haxProps.gizmo.handles; - - const audioHandle = handles.find(handle => handle.type === "audio"); + + const audioHandle = handles.find((handle) => handle.type === "audio"); expect(audioHandle).to.exist; expect(audioHandle.type_exclusive).to.be.true; expect(audioHandle.source).to.equal("source"); @@ -259,26 +269,30 @@ describe("audio-player test", () => { it("should have proper HAX settings configuration", () => { const haxProps = element.constructor.haxProperties; const configItems = haxProps.settings.configure; - + // Verify source property configuration - const sourceProp = configItems.find(item => item.property === "source"); + const sourceProp = configItems.find((item) => item.property === "source"); expect(sourceProp).to.exist; expect(sourceProp.inputMethod).to.equal("haxupload"); expect(sourceProp.noCamera).to.be.true; expect(sourceProp.validationType).to.equal("url"); - + // Verify mediaTitle property - const titleProp = configItems.find(item => item.property === "mediaTitle"); + const titleProp = configItems.find( + (item) => item.property === "mediaTitle", + ); expect(titleProp).to.exist; expect(titleProp.inputMethod).to.equal("textfield"); - + // Verify accentColor property - const colorProp = configItems.find(item => item.property === "accentColor"); + const colorProp = configItems.find( + (item) => item.property === "accentColor", + ); expect(colorProp).to.exist; expect(colorProp.inputMethod).to.equal("colorpicker"); - + // Verify track property for captions - const trackProp = configItems.find(item => item.property === "track"); + const trackProp = configItems.find((item) => item.property === "track"); expect(trackProp).to.exist; expect(trackProp.noVoiceRecord).to.be.true; }); @@ -286,7 +300,7 @@ describe("audio-player test", () => { it("should maintain accessibility with HAX demo schema", async () => { const demoSchema = element.constructor.haxProperties.demoSchema[0]; const haxTestElement = await fixture(html` - { it("should remain accessible with invalid source URL", async () => { const testElement = await fixture(html` - @@ -328,7 +342,7 @@ describe("audio-player test", () => { it("should handle malformed caption files", async () => { const testElement = await fixture(html` - { it("should handle unusual property values", async () => { const testElement = await fixture(html``); - + const edgeCaseValues = [ " \t\n ", // whitespace "🎵 audio with emoji 🎶", // emoji "Very long audio title that might cause layout issues or other display problems", "Multi\nline\ntitle", // multiline - "Title with 'quotes' and \"double quotes\" and special chars: !@#$%^&*()" + "Title with 'quotes' and \"double quotes\" and special chars: !@#$%^&*()", ]; - + for (const value of edgeCaseValues) { testElement.mediaTitle = value; testElement.source = value; await testElement.updateComplete; - + expect(testElement.mediaTitle).to.equal(value); expect(testElement.source).to.equal(value); await expect(testElement).shadowDom.to.be.accessible(); @@ -366,15 +380,15 @@ describe("audio-player test", () => { const testElement = await fixture(html` `); - + // Test during initialization expect(testElement.audioOnly).to.be.true; - + // Test after update testElement.source = "https://example.com/another-audio.mp3"; await testElement.updateComplete; expect(testElement.audioOnly).to.be.true; - + // Test after property changes testElement.mediaTitle = "New Title"; testElement.accentColor = "purple"; @@ -384,13 +398,13 @@ describe("audio-player test", () => { it("should handle background playback settings", async () => { const testElement = await fixture(html` - `); await testElement.updateComplete; - + expect(testElement.allowBackgroundPlay).to.be.true; expect(testElement.audioOnly).to.be.true; await expect(testElement).shadowDom.to.be.accessible(); @@ -398,14 +412,14 @@ describe("audio-player test", () => { it("should support learning mode restrictions", async () => { const testElement = await fixture(html` - `); await testElement.updateComplete; - + expect(testElement.learningMode).to.be.true; expect(testElement.audioOnly).to.be.true; await expect(testElement).shadowDom.to.be.accessible(); @@ -414,22 +428,22 @@ describe("audio-player test", () => { describe("Constructor and inheritance behavior", () => { it("should call super() in constructor", () => { - const testElement = new (element.constructor)(); + const testElement = new element.constructor(); expect(testElement).to.be.instanceOf(element.constructor); }); it("should maintain audioOnly getter behavior", () => { - const testElement = new (element.constructor)(); - + const testElement = new element.constructor(); + // Test getter behavior expect(testElement.audioOnly).to.be.true; - + // Attempt to override (should still return true) - Object.defineProperty(testElement, 'audioOnly', { + Object.defineProperty(testElement, "audioOnly", { value: false, - writable: true + writable: true, }); - + // Getter should still return true expect(testElement.audioOnly).to.be.true; }); @@ -437,18 +451,25 @@ describe("audio-player test", () => { describe("Customization and theming", () => { it("should support custom accent colors", async () => { - const colors = ['red', 'blue', 'green', '#FF5733', 'rgb(255, 87, 51)', 'hsl(9, 100%, 60%)']; - + const colors = [ + "red", + "blue", + "green", + "#FF5733", + "rgb(255, 87, 51)", + "hsl(9, 100%, 60%)", + ]; + for (const color of colors) { const testElement = await fixture(html` - `); await testElement.updateComplete; - + expect(testElement.accentColor).to.equal(color); await expect(testElement).shadowDom.to.be.accessible(); } @@ -456,7 +477,7 @@ describe("audio-player test", () => { it("should support transcript customization", async () => { const testElement = await fixture(html` - { > `); await testElement.updateComplete; - + expect(testElement.darkTranscript).to.be.true; expect(testElement.hideTimestamps).to.be.true; expect(testElement.disableInteractive).to.be.true; diff --git a/elements/awesome-explosion/.gitignore b/elements/awesome-explosion/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/awesome-explosion/.gitignore +++ b/elements/awesome-explosion/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/awesome-explosion/awesome-explosion.js b/elements/awesome-explosion/awesome-explosion.js index 9696623237..239419bebb 100644 --- a/elements/awesome-explosion/awesome-explosion.js +++ b/elements/awesome-explosion/awesome-explosion.js @@ -3,6 +3,7 @@ * @license Apache-2.0, see License.md for full text. */ import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; /** * `awesome-explosion` * `An awesome, explosion.` @@ -11,55 +12,108 @@ import { LitElement, html, css } from "lit"; * @demo demo/index.html * @element awesome-explosion */ -class AwesomeExplosion extends LitElement { +class AwesomeExplosion extends DDDSuper(LitElement) { /** * LitElement constructable styles enhancement */ static get styles() { return [ + super.styles, css` :host { display: inline-block; + cursor: pointer; + transition: transform var(--ddd-duration-fast) var(--ddd-timing-ease); } + :host(:hover) { + transform: scale(1.05); + } + :host(:focus-visible) { + outline: var(--ddd-focus-ring); + outline-offset: var(--ddd-focus-offset); + } + + /* DDD-based sizing using icon variables */ :host([size="tiny"]) #image { - width: 80px; - height: 80px; + width: var(--ddd-icon-sm); + height: var(--ddd-icon-sm); } :host([size="small"]) #image { - width: 160px; - height: 160px; + width: var(--ddd-icon-lg); + height: var(--ddd-icon-lg); } :host([size="medium"]) #image { - width: 240px; - height: 240px; + width: var(--ddd-icon-xl); + height: var(--ddd-icon-xl); } :host([size="large"]) #image { - width: 320px; - height: 320px; + width: var(--ddd-icon-2xl); + height: var(--ddd-icon-2xl); } :host([size="epic"]) #image { - width: 720px; - height: 720px; + width: var(--ddd-icon-4xl); + height: var(--ddd-icon-4xl); } + /* DDD-based color theming with dark mode support */ :host([color="red"]) #image { - filter: sepia() saturate(10000%) hue-rotate(30deg); + filter: sepia() saturate(10000%) hue-rotate(30deg) brightness(0.9); } :host([color="purple"]) #image { - filter: sepia() saturate(10000%) hue-rotate(290deg); + filter: sepia() saturate(10000%) hue-rotate(290deg) brightness(0.9); } :host([color="blue"]) #image { - filter: sepia() saturate(10000%) hue-rotate(210deg); + filter: sepia() saturate(10000%) hue-rotate(210deg) brightness(0.9); } :host([color="orange"]) #image { - filter: sepia() saturate(10000%) hue-rotate(320deg); + filter: sepia() saturate(10000%) hue-rotate(320deg) brightness(0.9); } :host([color="yellow"]) #image { - filter: sepia() saturate(10000%) hue-rotate(70deg); + filter: sepia() saturate(10000%) hue-rotate(70deg) brightness(0.9); + } + + /* Dark mode adjustments */ + @media (prefers-color-scheme: dark) { + :host([color]) #image { + filter-brightness: 1.2; + } } + + /* Respect reduced motion preference */ + @media (prefers-reduced-motion: reduce) { + :host { + transition: none; + } + :host(:hover) { + transform: none; + } + #image { + animation: none !important; + } + } + #image { - width: 240px; - height: 240px; + width: var(--ddd-icon-xl); + height: var(--ddd-icon-xl); + border-radius: var(--ddd-radius-sm); + transition: filter var(--ddd-duration-fast) var(--ddd-timing-ease); + } + + /* Accessibility improvements */ + :host([disabled]) { + pointer-events: none; + opacity: var(--ddd-opacity-40); + } + + .visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; } `, ]; @@ -75,10 +129,25 @@ class AwesomeExplosion extends LitElement { this.size = "medium"; this.color = ""; this.resetSound = false; + this.soundEnabled = true; + this.disabled = false; + this.tabIndex = 0; + + // Check for user preferences + this._checkUserPreferences(); + setTimeout(() => { - this.addEventListener("click", this._setPlaySound.bind(this)); - this.addEventListener("mouseover", this._setPlaySound.bind(this)); - this.addEventListener("mouseout", this._setStopSound.bind(this)); + this.addEventListener("click", this._handleClick.bind(this)); + this.addEventListener("keydown", this._handleKeydown.bind(this)); + + // Only add hover listeners if sound is enabled and motion is not reduced + if ( + this.soundEnabled && + !globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches + ) { + this.addEventListener("mouseover", this._handleMouseOver.bind(this)); + this.addEventListener("mouseout", this._handleMouseOut.bind(this)); + } }, 0); } render() { @@ -88,14 +157,30 @@ class AwesomeExplosion extends LitElement { src="${this.image}" id="image" class="image-tag" - alt="" + alt="${this._getAltText()}" + role="button" + aria-pressed="${this.playing ? "true" : "false"}" + aria-label="${this._getAriaLabel()}" /> + + ${this.soundEnabled + ? "Click or hover to play explosion sound" + : "Visual explosion effect (sound disabled)"} + `; } static get tag() { return "awesome-explosion"; } + + /** + * HAXSchema for proper integration with the HAX editor + */ + static get haxProperties() { + return new URL(`./lib/${this.tag}.haxProperties.json`, import.meta.url) + .href; + } updated(changedProperties) { changedProperties.forEach((oldValue, propName) => { if (propName == "state") { @@ -107,6 +192,7 @@ class AwesomeExplosion extends LitElement { } static get properties() { return { + ...super.properties, /** * State is for setting: * Possible values: play, pause, stop @@ -155,7 +241,7 @@ class AwesomeExplosion extends LitElement { }, /** * This is to change the color of the element. Possible values are: - * red, blue, orange, yellow + * red, blue, orange, yellow, purple */ color: { type: String, @@ -169,6 +255,21 @@ class AwesomeExplosion extends LitElement { reflect: true, attribute: "reset-sound", }, + /** + * Enable/disable sound effects globally + */ + soundEnabled: { + type: Boolean, + reflect: true, + attribute: "sound-enabled", + }, + /** + * Disable the entire component + */ + disabled: { + type: Boolean, + reflect: true, + }, }; } @@ -247,6 +348,93 @@ class AwesomeExplosion extends LitElement { } } + /** + * Handle click events with accessibility considerations + */ + _handleClick(e) { + if (this.disabled) return; + + if (this.state === "play") { + this.state = "stop"; + } else { + this.state = "play"; + } + } + + /** + * Handle keyboard interactions + */ + _handleKeydown(e) { + if (this.disabled) return; + + // Space or Enter key + if (e.key === " " || e.key === "Enter") { + e.preventDefault(); + this._handleClick(e); + } + } + + /** + * Handle mouse over with reduced motion consideration + */ + _handleMouseOver(e) { + if (this.disabled || !this.soundEnabled) return; + + // Only play on hover if not reduced motion + if (!globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches) { + this.state = "play"; + } + } + + /** + * Handle mouse out + */ + _handleMouseOut(e) { + if (this.disabled) return; + this.state = "pause"; + } + + /** + * Check user preferences for accessibility + */ + _checkUserPreferences() { + // Check for reduced motion preference + const prefersReducedMotion = globalThis.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches; + + // Check for user sound preference (could be stored in localStorage) + const userSoundPref = + globalThis.localStorage && + globalThis.localStorage.getItem("awesome-explosion-sound-enabled"); + if (userSoundPref !== null) { + this.soundEnabled = userSoundPref === "true"; + } + + // Disable sound on hover if reduced motion is preferred + if (prefersReducedMotion) { + this.soundEnabled = false; + } + } + + /** + * Get appropriate alt text for the image + */ + _getAltText() { + const colorText = this.color ? ` ${this.color}` : ""; + const sizeText = this.size !== "medium" ? ` ${this.size}` : ""; + return `${sizeText}${colorText} explosion animation`; + } + + /** + * Get appropriate ARIA label + */ + _getAriaLabel() { + const stateText = this.playing ? "playing" : "stopped"; + const soundText = this.soundEnabled ? " with sound" : " (muted)"; + return `Explosion animation ${stateText}${soundText}. Click to toggle.`; + } + /** * Set the state to play from an event. */ @@ -255,20 +443,34 @@ class AwesomeExplosion extends LitElement { } /** - * Set the state to play from an event. + * Set the state to stop from an event. */ _setStopSound(e) { this.state = "pause"; } /** - * Play the sound effect. + * Play the sound effect with accessibility considerations. */ _playSound() { + if (!this.soundEnabled || this.disabled) return; + + // Respect reduced motion preference + if (globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches) { + return; + } + if (typeof globalThis.audio === typeof undefined) { globalThis.audio = new Audio(this.sound); + // Set volume to a reasonable level + globalThis.audio.volume = 0.2; } - globalThis.audio.play(); + + // Catch and handle audio play errors gracefully + globalThis.audio.play().catch((error) => { + console.warn("Audio playback failed:", error); + // Could dispatch an event here to notify parent components + }); } } globalThis.customElements.define(AwesomeExplosion.tag, AwesomeExplosion); diff --git a/elements/awesome-explosion/custom-elements.json b/elements/awesome-explosion/custom-elements.json old mode 100755 new mode 100644 index 57cfeb2520..730c6c519d --- a/elements/awesome-explosion/custom-elements.json +++ b/elements/awesome-explosion/custom-elements.json @@ -17,6 +17,13 @@ "static": true, "readonly": true }, + { + "kind": "field", + "name": "haxProperties", + "static": true, + "description": "HAXSchema for proper integration with the HAX editor", + "readonly": true + }, { "kind": "method", "name": "_calculateStopped", @@ -61,6 +68,61 @@ "name": "_stopSound", "description": "Stop the sound effect." }, + { + "kind": "method", + "name": "_handleClick", + "parameters": [ + { + "name": "e" + } + ], + "description": "Handle click events with accessibility considerations" + }, + { + "kind": "method", + "name": "_handleKeydown", + "parameters": [ + { + "name": "e" + } + ], + "description": "Handle keyboard interactions" + }, + { + "kind": "method", + "name": "_handleMouseOver", + "parameters": [ + { + "name": "e" + } + ], + "description": "Handle mouse over with reduced motion consideration" + }, + { + "kind": "method", + "name": "_handleMouseOut", + "parameters": [ + { + "name": "e" + } + ], + "description": "Handle mouse out" + }, + { + "kind": "method", + "name": "_checkUserPreferences", + "description": "Check user preferences for accessibility" + }, + { + "kind": "method", + "name": "_getAltText", + "description": "Get appropriate alt text for the image" + }, + { + "kind": "method", + "name": "_getAriaLabel", + "description": "Get appropriate ARIA label" + }, { "kind": "method", "name": "_setPlaySound", @@ -79,12 +141,12 @@ "name": "e" } ], - "description": "Set the state to play from an event." + "description": "Set the state to stop from an event." }, { "kind": "method", "name": "_playSound", - "description": "Play the sound effect." + "description": "Play the sound effect with accessibility considerations." }, { "kind": "field", @@ -137,7 +199,7 @@ "type": { "text": "string" }, - "description": "This is to change the color of the element. Possible values are:\nred, blue, orange, yellow", + "description": "This is to change the color of the element. Possible values are:\nred, blue, orange, yellow, purple", "default": "\"\"", "attribute": "color", "reflects": true @@ -154,6 +216,38 @@ "attribute": "reset-sound", "reflects": true }, + { + "kind": "field", + "name": "soundEnabled", + "privacy": "public", + "type": { + "text": "boolean" + }, + "description": "Enable/disable sound effects globally", + "default": "true", + "attribute": "sound-enabled", + "reflects": true + }, + { + "kind": "field", + "name": "disabled", + "privacy": "public", + "type": { + "text": "boolean" + }, + "description": "Disable the entire component", + "default": "false", + "attribute": "disabled", + "reflects": true + }, + { + "kind": "field", + "name": "tabIndex", + "type": { + "text": "number" + }, + "default": "0" + }, { "kind": "field", "name": "stopped", @@ -257,7 +351,7 @@ "type": { "text": "string" }, - "description": "This is to change the color of the element. Possible values are:\nred, blue, orange, yellow", + "description": "This is to change the color of the element. Possible values are:\nred, blue, orange, yellow, purple", "default": "\"\"", "fieldName": "color" }, @@ -269,6 +363,30 @@ "description": "Allow for resetting the sound effect.", "default": "false", "fieldName": "resetSound" + }, + { + "name": "sound-enabled", + "type": { + "text": "boolean" + }, + "description": "Enable/disable sound effects globally", + "default": "true", + "fieldName": "soundEnabled" + }, + { + "name": "disabled", + "type": { + "text": "boolean" + }, + "description": "Disable the entire component", + "default": "false", + "fieldName": "disabled" + } + ], + "mixins": [ + { + "name": "DDDSuper", + "package": "@haxtheweb/d-d-d/d-d-d.js" } ], "superclass": { diff --git a/elements/awesome-explosion/demo/index.html b/elements/awesome-explosion/demo/index.html index 675bb5393b..a188257342 100644 --- a/elements/awesome-explosion/demo/index.html +++ b/elements/awesome-explosion/demo/index.html @@ -7,24 +7,93 @@ - +
-

A bunch of awesome-explosion tags

+

Awesome Explosion - DDD Design System & Accessibility Enhanced

+ + + + + + + + + + +
+

Accessibility Notes:

+
    +
  • ✅ Uses DDD design system variables for consistent sizing
  • +
  • ✅ Respects prefers-reduced-motion settings
  • +
  • ✅ Keyboard accessible (Tab + Enter/Space)
  • +
  • ✅ Screen reader friendly with proper ARIA labels
  • +
  • ✅ Sound can be controlled globally via attributes
  • +
  • ✅ Dark mode compatible with DDD theming
  • +
  • ✅ Focus indicators follow DDD focus system
  • +
+
diff --git a/elements/awesome-explosion/index.html b/elements/awesome-explosion/index.html index dfcb41285c..da8fbff977 100755 --- a/elements/awesome-explosion/index.html +++ b/elements/awesome-explosion/index.html @@ -4,10 +4,10 @@ awesome-explosion documentation - - + + - + diff --git a/elements/awesome-explosion/lib/awesome-explosion.haxProperties.json b/elements/awesome-explosion/lib/awesome-explosion.haxProperties.json new file mode 100644 index 0000000000..136ef9d579 --- /dev/null +++ b/elements/awesome-explosion/lib/awesome-explosion.haxProperties.json @@ -0,0 +1,116 @@ +{ + "api": "1", + "type": "element", + "editingElement": "core", + "canScale": false, + "canPosition": true, + "canEditSource": false, + "gizmo": { + "title": "Awesome Explosion", + "description": "An interactive explosion animation with optional sound effects", + "icon": "av:fiber-manual-record", + "color": "orange", + "tags": ["Silly", "Animation", "Interactive", "Sound", "Effect"], + "handles": [], + "meta": { + "author": "HAX The Web", + "inlineOnly": false, + "requiresChildren": false + } + }, + "settings": { + "configure": [ + { + "property": "size", + "title": "Size", + "description": "Control the size of the explosion", + "inputMethod": "select", + "options": { + "tiny": "Tiny", + "small": "Small", + "medium": "Medium", + "large": "Large", + "epic": "Epic" + } + }, + { + "property": "color", + "title": "Color", + "description": "Color theme for the explosion", + "inputMethod": "select", + "options": { + "": "Default", + "red": "Red", + "blue": "Blue", + "purple": "Purple", + "orange": "Orange", + "yellow": "Yellow" + } + }, + { + "property": "soundEnabled", + "title": "Sound Effects", + "description": "Enable or disable sound effects", + "inputMethod": "boolean" + }, + { + "property": "resetSound", + "title": "Reset Sound", + "description": "Reset sound to beginning each time", + "inputMethod": "boolean" + }, + { + "property": "image", + "title": "Custom Image", + "description": "Optional custom explosion image URL", + "inputMethod": "haxupload", + "validationType": "url" + }, + { + "property": "sound", + "title": "Custom Sound", + "description": "Optional custom sound effect URL", + "inputMethod": "haxupload", + "validationType": "url" + } + ], + "advanced": [ + { + "property": "disabled", + "title": "Disabled", + "description": "Disable all interactions", + "inputMethod": "boolean" + } + ] + }, + "demoSchema": [ + { + "tag": "awesome-explosion", + "properties": { + "size": "medium", + "color": "orange" + }, + "content": "", + "description": "A medium orange explosion" + }, + { + "tag": "awesome-explosion", + "properties": { + "size": "large", + "color": "blue", + "soundEnabled": false + }, + "content": "", + "description": "A large blue explosion without sound" + }, + { + "tag": "awesome-explosion", + "properties": { + "size": "epic", + "color": "red" + }, + "content": "", + "description": "An epic red explosion with sound" + } + ] +} diff --git a/elements/awesome-explosion/package.json b/elements/awesome-explosion/package.json old mode 100755 new mode 100644 index 53e8fb4bdc..92205afc99 --- a/elements/awesome-explosion/package.json +++ b/elements/awesome-explosion/package.json @@ -37,16 +37,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/awesome-explosion/test/awesome-explosion.test.js b/elements/awesome-explosion/test/awesome-explosion.test.js index 77b278e8aa..e5c3c97bb8 100644 --- a/elements/awesome-explosion/test/awesome-explosion.test.js +++ b/elements/awesome-explosion/test/awesome-explosion.test.js @@ -4,26 +4,26 @@ import "../awesome-explosion.js"; describe("awesome-explosion test", () => { let element; let originalAudio; - + beforeEach(async () => { // Mock global audio to prevent actual sound playback during tests originalAudio = globalThis.audio; globalThis.audio = { play: () => Promise.resolve(), pause: () => {}, - currentTime: 0 + currentTime: 0, }; - + element = await fixture(html` - `); await element.updateComplete; }); - + afterEach(() => { // Restore original global audio globalThis.audio = originalAudio; @@ -51,10 +51,10 @@ describe("awesome-explosion test", () => { }); it("should have default image and sound URLs", () => { - expect(element.image).to.be.a('string'); - expect(element.image).to.include('explode.gif'); - expect(element.sound).to.be.a('string'); - expect(element.sound).to.include('.mp3'); + expect(element.image).to.be.a("string"); + expect(element.image).to.include("explode.gif"); + expect(element.sound).to.be.a("string"); + expect(element.sound).to.include(".mp3"); }); }); @@ -70,14 +70,14 @@ describe("awesome-explosion test", () => { describe("Size property", () => { it("should handle all valid size values and maintain accessibility", async () => { - const validSizes = ['tiny', 'small', 'medium', 'large', 'epic']; - + const validSizes = ["tiny", "small", "medium", "large", "epic"]; + for (const size of validSizes) { testElement.size = size; await testElement.updateComplete; expect(testElement.size).to.equal(size); - expect(testElement.hasAttribute('size')).to.be.true; - expect(testElement.getAttribute('size')).to.equal(size); + expect(testElement.hasAttribute("size")).to.be.true; + expect(testElement.getAttribute("size")).to.equal(size); await expect(testElement).shadowDom.to.be.accessible(); } }); @@ -89,15 +89,15 @@ describe("awesome-explosion test", () => { describe("Color property", () => { it("should handle all valid color values and maintain accessibility", async () => { - const validColors = ['red', 'purple', 'blue', 'orange', 'yellow', '']; - + const validColors = ["red", "purple", "blue", "orange", "yellow", ""]; + for (const color of validColors) { testElement.color = color; await testElement.updateComplete; expect(testElement.color).to.equal(color); if (color) { - expect(testElement.hasAttribute('color')).to.be.true; - expect(testElement.getAttribute('color')).to.equal(color); + expect(testElement.hasAttribute("color")).to.be.true; + expect(testElement.getAttribute("color")).to.equal(color); } await expect(testElement).shadowDom.to.be.accessible(); } @@ -110,14 +110,14 @@ describe("awesome-explosion test", () => { describe("State property", () => { it("should handle all valid state values and maintain accessibility", async () => { - const validStates = ['play', 'pause', 'stop']; - + const validStates = ["play", "pause", "stop"]; + for (const state of validStates) { testElement.state = state; await testElement.updateComplete; expect(testElement.state).to.equal(state); - expect(testElement.hasAttribute('state')).to.be.true; - expect(testElement.getAttribute('state')).to.equal(state); + expect(testElement.hasAttribute("state")).to.be.true; + expect(testElement.getAttribute("state")).to.equal(state); await expect(testElement).shadowDom.to.be.accessible(); } }); @@ -132,33 +132,33 @@ describe("awesome-explosion test", () => { testElement.resetSound = true; await testElement.updateComplete; expect(testElement.resetSound).to.equal(true); - expect(testElement.hasAttribute('reset-sound')).to.be.true; + expect(testElement.hasAttribute("reset-sound")).to.be.true; await expect(testElement).shadowDom.to.be.accessible(); testElement.resetSound = false; await testElement.updateComplete; expect(testElement.resetSound).to.equal(false); - expect(testElement.hasAttribute('reset-sound')).to.be.false; + expect(testElement.hasAttribute("reset-sound")).to.be.false; await expect(testElement).shadowDom.to.be.accessible(); }); it("should handle computed boolean properties", async () => { // Test stopped state - testElement.state = 'stop'; + testElement.state = "stop"; await testElement.updateComplete; expect(testElement.stopped).to.be.true; expect(testElement.playing).to.be.false; expect(testElement.paused).to.be.false; // Test playing state - testElement.state = 'play'; + testElement.state = "play"; await testElement.updateComplete; expect(testElement.stopped).to.be.false; expect(testElement.playing).to.be.true; expect(testElement.paused).to.be.false; // Test paused state - testElement.state = 'pause'; + testElement.state = "pause"; await testElement.updateComplete; expect(testElement.stopped).to.be.false; expect(testElement.playing).to.be.false; @@ -172,9 +172,11 @@ describe("awesome-explosion test", () => { it("should handle custom image property", async () => { testElement.image = "https://example.com/custom-explosion.gif"; await testElement.updateComplete; - expect(testElement.image).to.equal("https://example.com/custom-explosion.gif"); - - const img = testElement.shadowRoot.querySelector('#image'); + expect(testElement.image).to.equal( + "https://example.com/custom-explosion.gif", + ); + + const img = testElement.shadowRoot.querySelector("#image"); expect(img.src).to.equal("https://example.com/custom-explosion.gif"); await expect(testElement).shadowDom.to.be.accessible(); }); @@ -182,7 +184,9 @@ describe("awesome-explosion test", () => { it("should handle custom sound property", async () => { testElement.sound = "https://example.com/custom-explosion.mp3"; await testElement.updateComplete; - expect(testElement.sound).to.equal("https://example.com/custom-explosion.mp3"); + expect(testElement.sound).to.equal( + "https://example.com/custom-explosion.mp3", + ); await expect(testElement).shadowDom.to.be.accessible(); }); }); @@ -190,48 +194,50 @@ describe("awesome-explosion test", () => { describe("Visual rendering and image display", () => { it("should render image element with correct attributes", () => { - const img = element.shadowRoot.querySelector('#image'); + const img = element.shadowRoot.querySelector("#image"); expect(img).to.exist; - expect(img.tagName.toLowerCase()).to.equal('img'); - expect(img.getAttribute('loading')).to.equal('lazy'); - expect(img.getAttribute('alt')).to.equal(''); - expect(img.classList.contains('image-tag')).to.be.true; + expect(img.tagName.toLowerCase()).to.equal("img"); + expect(img.getAttribute("loading")).to.equal("lazy"); + expect(img.getAttribute("alt")).to.equal(""); + expect(img.classList.contains("image-tag")).to.be.true; }); it("should update image source when image property changes", async () => { const testElement = await fixture(html` - + `); await testElement.updateComplete; - - const img = testElement.shadowRoot.querySelector('#image'); + + const img = testElement.shadowRoot.querySelector("#image"); expect(img.src).to.equal("https://example.com/test.gif"); }); it("should apply size-based CSS classes correctly", async () => { - const sizes = ['tiny', 'small', 'medium', 'large', 'epic']; - + const sizes = ["tiny", "small", "medium", "large", "epic"]; + for (const size of sizes) { const testElement = await fixture(html` `); await testElement.updateComplete; - - expect(testElement.getAttribute('size')).to.equal(size); + + expect(testElement.getAttribute("size")).to.equal(size); await expect(testElement).shadowDom.to.be.accessible(); } }); it("should apply color-based CSS filters correctly", async () => { - const colors = ['red', 'purple', 'blue', 'orange', 'yellow']; - + const colors = ["red", "purple", "blue", "orange", "yellow"]; + for (const color of colors) { const testElement = await fixture(html` `); await testElement.updateComplete; - - expect(testElement.getAttribute('color')).to.equal(color); + + expect(testElement.getAttribute("color")).to.equal(color); await expect(testElement).shadowDom.to.be.accessible(); } }); @@ -239,11 +245,11 @@ describe("awesome-explosion test", () => { describe("Sound functionality and audio controls", () => { let mockAudio; - + beforeEach(() => { let playCallCount = 0; let pauseCallCount = 0; - + mockAudio = { play: () => { playCallCount++; @@ -254,9 +260,9 @@ describe("awesome-explosion test", () => { }, currentTime: 0, getPlayCallCount: () => playCallCount, - getPauseCallCount: () => pauseCallCount + getPauseCallCount: () => pauseCallCount, }; - + globalThis.audio = mockAudio; }); @@ -264,10 +270,10 @@ describe("awesome-explosion test", () => { const testElement = await fixture(html` `); - - testElement.state = 'play'; + + testElement.state = "play"; await testElement.updateComplete; - + expect(mockAudio.getPlayCallCount()).to.be.greaterThan(0); expect(testElement.playing).to.be.true; }); @@ -276,10 +282,10 @@ describe("awesome-explosion test", () => { const testElement = await fixture(html` `); - - testElement.state = 'pause'; + + testElement.state = "pause"; await testElement.updateComplete; - + expect(mockAudio.getPauseCallCount()).to.be.greaterThan(0); expect(testElement.paused).to.be.true; }); @@ -288,10 +294,10 @@ describe("awesome-explosion test", () => { const testElement = await fixture(html` `); - - testElement.state = 'stop'; + + testElement.state = "stop"; await testElement.updateComplete; - + expect(mockAudio.getPauseCallCount()).to.be.greaterThan(0); expect(testElement.stopped).to.be.true; expect(mockAudio.currentTime).to.equal(0); @@ -301,85 +307,85 @@ describe("awesome-explosion test", () => { const testElement = await fixture(html` `); - + mockAudio.currentTime = 5.5; // Set some progress - testElement.state = 'pause'; + testElement.state = "pause"; await testElement.updateComplete; - + expect(mockAudio.currentTime).to.equal(0); }); it("should create new Audio instance if not exists", async () => { delete globalThis.audio; - + const testElement = await fixture(html` `); - + // Mock the Audio constructor const originalAudio = globalThis.Audio; let audioCreated = false; - globalThis.Audio = function(src) { + globalThis.Audio = function (src) { audioCreated = true; this.play = () => Promise.resolve(); this.pause = () => {}; this.currentTime = 0; return this; }; - - testElement.state = 'play'; + + testElement.state = "play"; await testElement.updateComplete; - + expect(audioCreated).to.be.true; - + globalThis.Audio = originalAudio; }); }); describe("Event handling and user interaction", () => { it("should respond to click events", (done) => { - const testElement = new (element.constructor)(); - + const testElement = new element.constructor(); + // Wait for event listeners to be attached setTimeout(() => { - expect(testElement.state).to.equal('stop'); - + expect(testElement.state).to.equal("stop"); + testElement.click(); - + setTimeout(() => { - expect(testElement.state).to.equal('play'); + expect(testElement.state).to.equal("play"); done(); }, 10); }, 10); }); it("should respond to mouseover events", (done) => { - const testElement = new (element.constructor)(); - + const testElement = new element.constructor(); + setTimeout(() => { - expect(testElement.state).to.equal('stop'); - - const mouseoverEvent = new MouseEvent('mouseover'); + expect(testElement.state).to.equal("stop"); + + const mouseoverEvent = new MouseEvent("mouseover"); testElement.dispatchEvent(mouseoverEvent); - + setTimeout(() => { - expect(testElement.state).to.equal('play'); + expect(testElement.state).to.equal("play"); done(); }, 10); }, 10); }); it("should respond to mouseout events", (done) => { - const testElement = new (element.constructor)(); - + const testElement = new element.constructor(); + setTimeout(() => { - testElement.state = 'play'; - - const mouseoutEvent = new MouseEvent('mouseout'); + testElement.state = "play"; + + const mouseoutEvent = new MouseEvent("mouseout"); testElement.dispatchEvent(mouseoutEvent); - + setTimeout(() => { - expect(testElement.state).to.equal('pause'); + expect(testElement.state).to.equal("pause"); done(); }, 10); }, 10); @@ -388,46 +394,46 @@ describe("awesome-explosion test", () => { describe("Custom events and communication", () => { it("should dispatch awesome-event when playing", (done) => { - const testElement = new (element.constructor)(); - - testElement.addEventListener('awesome-event', (e) => { - expect(e.detail.message).to.equal('Sound played'); + const testElement = new element.constructor(); + + testElement.addEventListener("awesome-event", (e) => { + expect(e.detail.message).to.equal("Sound played"); expect(e.bubbles).to.be.true; expect(e.cancelable).to.be.true; expect(e.composed).to.be.true; done(); }); - - testElement.state = 'play'; + + testElement.state = "play"; }); it("should dispatch awesome-event when paused", (done) => { - const testElement = new (element.constructor)(); - - testElement.addEventListener('awesome-event', (e) => { - expect(e.detail.message).to.equal('Sound paused'); + const testElement = new element.constructor(); + + testElement.addEventListener("awesome-event", (e) => { + expect(e.detail.message).to.equal("Sound paused"); done(); }); - - testElement.state = 'pause'; + + testElement.state = "pause"; }); it("should dispatch awesome-event when stopped", (done) => { - const testElement = new (element.constructor)(); - - testElement.addEventListener('awesome-event', (e) => { - expect(e.detail.message).to.equal('Sound stopped'); + const testElement = new element.constructor(); + + testElement.addEventListener("awesome-event", (e) => { + expect(e.detail.message).to.equal("Sound stopped"); done(); }); - - testElement.state = 'stop'; + + testElement.state = "stop"; }); }); describe("Accessibility scenarios", () => { it("should remain accessible with different sizes", async () => { - const sizes = ['tiny', 'small', 'medium', 'large', 'epic']; - + const sizes = ["tiny", "small", "medium", "large", "epic"]; + for (const size of sizes) { const testElement = await fixture(html` @@ -438,8 +444,8 @@ describe("awesome-explosion test", () => { }); it("should remain accessible with different colors", async () => { - const colors = ['red', 'purple', 'blue', 'orange', 'yellow']; - + const colors = ["red", "purple", "blue", "orange", "yellow"]; + for (const color of colors) { const testElement = await fixture(html` @@ -453,8 +459,8 @@ describe("awesome-explosion test", () => { const testElement = await fixture(html` `); - - const states = ['play', 'pause', 'stop']; + + const states = ["play", "pause", "stop"]; for (const state of states) { testElement.state = state; await testElement.updateComplete; @@ -479,9 +485,9 @@ describe("awesome-explosion test", () => { describe("Edge cases and error handling", () => { it("should handle missing global audio gracefully", () => { delete globalThis.audio; - - const testElement = new (element.constructor)(); - + + const testElement = new element.constructor(); + expect(() => { testElement._stopSound(); }).to.not.throw(); @@ -491,11 +497,11 @@ describe("awesome-explosion test", () => { const testElement = await fixture(html` `); - - testElement.state = 'invalid-state'; + + testElement.state = "invalid-state"; await testElement.updateComplete; - - expect(testElement.state).to.equal('invalid-state'); + + expect(testElement.state).to.equal("invalid-state"); expect(testElement.stopped).to.be.false; expect(testElement.playing).to.be.false; expect(testElement.paused).to.be.false; @@ -504,12 +510,15 @@ describe("awesome-explosion test", () => { it("should handle invalid size and color values", async () => { const testElement = await fixture(html` - + `); await testElement.updateComplete; - - expect(testElement.size).to.equal('invalid-size'); - expect(testElement.color).to.equal('invalid-color'); + + expect(testElement.size).to.equal("invalid-size"); + expect(testElement.color).to.equal("invalid-color"); await expect(testElement).shadowDom.to.be.accessible(); }); @@ -521,30 +530,32 @@ describe("awesome-explosion test", () => { > `); await testElement.updateComplete; - - expect(testElement.image).to.equal('invalid-url'); - expect(testElement.sound).to.equal('malformed-url'); + + expect(testElement.image).to.equal("invalid-url"); + expect(testElement.sound).to.equal("malformed-url"); await expect(testElement).shadowDom.to.be.accessible(); }); it("should handle unusual property values", async () => { - const testElement = await fixture(html``); - + const testElement = await fixture( + html``, + ); + const edgeCaseValues = [ " \t\n ", // whitespace "💥 explosion with emoji 💥", // emoji "Very long size name that might cause issues", "Multi\nline\nvalue", // multiline - "Value with 'quotes' and \"double quotes\" and special chars: !@#$%^&*()" + "Value with 'quotes' and \"double quotes\" and special chars: !@#$%^&*()", ]; - + for (const value of edgeCaseValues) { testElement.size = value; testElement.color = value; testElement.image = value; testElement.sound = value; await testElement.updateComplete; - + expect(testElement.size).to.equal(value); expect(testElement.color).to.equal(value); expect(testElement.image).to.equal(value); @@ -559,17 +570,17 @@ describe("awesome-explosion test", () => { const styles = element.constructor.styles; expect(styles).to.exist; expect(styles.length).to.be.greaterThan(0); - + const styleString = styles[0].cssText || styles[0].toString(); - expect(styleString).to.include(':host'); - expect(styleString).to.include('display: inline-block'); - expect(styleString).to.include('#image'); + expect(styleString).to.include(":host"); + expect(styleString).to.include("display: inline-block"); + expect(styleString).to.include("#image"); }); it("should include size-specific CSS rules", () => { const styles = element.constructor.styles; const styleString = styles[0].cssText || styles[0].toString(); - + expect(styleString).to.include(':host([size="tiny"])'); expect(styleString).to.include(':host([size="small"])'); expect(styleString).to.include(':host([size="medium"])'); @@ -580,39 +591,39 @@ describe("awesome-explosion test", () => { it("should include color filter CSS rules", () => { const styles = element.constructor.styles; const styleString = styles[0].cssText || styles[0].toString(); - + expect(styleString).to.include(':host([color="red"])'); expect(styleString).to.include(':host([color="purple"])'); expect(styleString).to.include(':host([color="blue"])'); expect(styleString).to.include(':host([color="orange"])'); expect(styleString).to.include(':host([color="yellow"])'); - expect(styleString).to.include('filter: sepia()'); + expect(styleString).to.include("filter: sepia()"); }); }); describe("Lifecycle and initialization", () => { it("should set up event listeners after timeout", (done) => { - const testElement = new (element.constructor)(); - + const testElement = new element.constructor(); + // Initially the event listeners shouldn't be set up yet - expect(testElement.state).to.equal('stop'); - + expect(testElement.state).to.equal("stop"); + // After timeout, event listeners should work setTimeout(() => { testElement.click(); setTimeout(() => { - expect(testElement.state).to.equal('play'); + expect(testElement.state).to.equal("play"); done(); }, 10); }, 10); }); it("should initialize with correct default media URLs", () => { - const testElement = new (element.constructor)(); - - expect(testElement.image).to.include('explode.gif'); - expect(testElement.sound).to.include('.mp3'); - expect(testElement.sound).to.include('fireworks'); + const testElement = new element.constructor(); + + expect(testElement.image).to.include("explode.gif"); + expect(testElement.sound).to.include(".mp3"); + expect(testElement.sound).to.include("fireworks"); }); }); }); diff --git a/elements/b-r/.gitignore b/elements/b-r/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/b-r/.gitignore +++ b/elements/b-r/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/b-r/demo/index.html b/elements/b-r/demo/index.html index 5c3c609c94..1a97b3b333 100644 --- a/elements/b-r/demo/index.html +++ b/elements/b-r/demo/index.html @@ -4,16 +4,15 @@ BR: b-r Demo - - +
diff --git a/elements/b-r/index.html b/elements/b-r/index.html index 831e26caab..c7737baf20 100644 --- a/elements/b-r/index.html +++ b/elements/b-r/index.html @@ -4,10 +4,10 @@ b-r documentation - - + + - + diff --git a/elements/b-r/package.json b/elements/b-r/package.json index 5049257c89..92b2da9bac 100644 --- a/elements/b-r/package.json +++ b/elements/b-r/package.json @@ -44,15 +44,12 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/barcode-reader/.gitignore b/elements/barcode-reader/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/barcode-reader/.gitignore +++ b/elements/barcode-reader/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/barcode-reader/demo/index.html b/elements/barcode-reader/demo/index.html index c286125546..6bffadc868 100644 --- a/elements/barcode-reader/demo/index.html +++ b/elements/barcode-reader/demo/index.html @@ -4,15 +4,14 @@ BarcodeReader: barcode-reader Demo - - +
diff --git a/elements/barcode-reader/index.html b/elements/barcode-reader/index.html index 08a49ac145..c7c828eb5a 100644 --- a/elements/barcode-reader/index.html +++ b/elements/barcode-reader/index.html @@ -4,10 +4,10 @@ barcode-reader documentation - - + + - + diff --git a/elements/barcode-reader/package.json b/elements/barcode-reader/package.json index ec88ac2cb1..9831e6f6a4 100644 --- a/elements/barcode-reader/package.json +++ b/elements/barcode-reader/package.json @@ -50,16 +50,13 @@ "@zxing/library": "0.19.1", "javascript-barcode-reader": "0.6.9", "jquery": "3.6.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "gulp-babel": "8.0.0", "wct-browser-legacy": "1.0.2" diff --git a/elements/barcode-reader/test/barcode-reader.test.js b/elements/barcode-reader/test/barcode-reader.test.js index d21a602d33..166dcefaa3 100644 --- a/elements/barcode-reader/test/barcode-reader.test.js +++ b/elements/barcode-reader/test/barcode-reader.test.js @@ -4,50 +4,54 @@ import "../barcode-reader.js"; // Mock the ZXing library and related globals beforeEach(() => { // Mock ZXing library - globalThis.ZXing = function() { + globalThis.ZXing = function () { return { Runtime: { - addFunction: (callback) => () => callback(0, 10, 0, 1) + addFunction: (callback) => () => callback(0, 10, 0, 1), }, HEAPU8: { - buffer: new ArrayBuffer(1000) + buffer: new ArrayBuffer(1000), }, _resize: (width, height) => 0, - _decode_any: (ptr) => 0 + _decode_any: (ptr) => 0, }; }; - + // Mock navigator.mediaDevices globalThis.navigator = globalThis.navigator || {}; globalThis.navigator.mediaDevices = { - enumerateDevices: () => Promise.resolve([ - { deviceId: 'camera1', kind: 'videoinput', label: 'Test Camera 1' }, - { deviceId: 'camera2', kind: 'videoinput', label: 'Test Camera 2' } - ]), - getUserMedia: (constraints) => Promise.resolve({ - getTracks: () => [{ - stop: () => {} - }] - }) + enumerateDevices: () => + Promise.resolve([ + { deviceId: "camera1", kind: "videoinput", label: "Test Camera 1" }, + { deviceId: "camera2", kind: "videoinput", label: "Test Camera 2" }, + ]), + getUserMedia: (constraints) => + Promise.resolve({ + getTracks: () => [ + { + stop: () => {}, + }, + ], + }), }; - + // Mock user agent for device detection - Object.defineProperty(globalThis.navigator, 'userAgent', { - get: () => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', - configurable: true + Object.defineProperty(globalThis.navigator, "userAgent", { + get: () => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36", + configurable: true, }); - + // Mock ESGlobalBridge globalThis.ESGlobalBridge = { requestAvailability: () => ({ load: (name, url) => { setTimeout(() => { - globalThis.dispatchEvent(new CustomEvent('es-bridge-zxing-loaded')); + globalThis.dispatchEvent(new CustomEvent("es-bridge-zxing-loaded")); }, 10); - } - }) + }, + }), }; - + // Mock stream globalThis.stream = null; }); @@ -60,7 +64,7 @@ afterEach(() => { describe("barcode-reader test", () => { let element; - + beforeEach(async () => { element = await fixture(html``); await element.updateComplete; @@ -92,7 +96,7 @@ describe("barcode-reader test", () => { const input = element.shadowRoot.querySelector('input[type="text"]'); const button = element.shadowRoot.querySelector("simple-icon-button"); const select = element.shadowRoot.querySelector("select"); - + expect(video).to.exist; expect(canvas).to.exist; expect(input).to.exist; @@ -109,13 +113,13 @@ describe("barcode-reader test", () => { "QR_CODE_DATA", "https://example.com", "Product SKU: ABC123", - "" + "", ]; - + for (const testValue of testValues) { element.value = testValue; await element.updateComplete; - + expect(element.value).to.equal(testValue); const input = element.shadowRoot.querySelector('input[type="text"]'); expect(input.value).to.equal(testValue); @@ -126,22 +130,22 @@ describe("barcode-reader test", () => { it("should reflect value property to attribute", async () => { element.value = "test-barcode-123"; await element.updateComplete; - + expect(element.getAttribute("value")).to.equal("test-barcode-123"); }); it("should dispatch value-changed event when value changes", async () => { let eventFired = false; let eventDetail = null; - - element.addEventListener('value-changed', (e) => { + + element.addEventListener("value-changed", (e) => { eventFired = true; eventDetail = e.detail; }); - + element.value = "new-barcode-value"; await element.updateComplete; - + expect(eventFired).to.be.true; expect(eventDetail).to.equal(element); }); @@ -150,15 +154,15 @@ describe("barcode-reader test", () => { describe("scale property", () => { it("should handle numeric scale values", async () => { const scaleValues = [50, 75, 100, 125, 150]; - + for (const scale of scaleValues) { element.scale = scale; await element.updateComplete; - + expect(element.scale).to.equal(scale); const video = element.shadowRoot.querySelector("video"); - expect(video.getAttribute('width')).to.equal(`${scale}%`); - expect(video.getAttribute('height')).to.equal(`${scale}%`); + expect(video.getAttribute("width")).to.equal(`${scale}%`); + expect(video.getAttribute("height")).to.equal(`${scale}%`); await expect(element).shadowDom.to.be.accessible(); } }); @@ -166,7 +170,7 @@ describe("barcode-reader test", () => { it("should reflect scale property to attribute", async () => { element.scale = 80; await element.updateComplete; - + expect(element.getAttribute("scale")).to.equal("80"); }); }); @@ -175,15 +179,15 @@ describe("barcode-reader test", () => { it("should control input visibility", async () => { // Input should be visible by default expect(element.hideinput).to.be.false; - let inputDiv = element.shadowRoot.querySelector('.input'); - expect(inputDiv.hasAttribute('hidden')).to.be.false; - + let inputDiv = element.shadowRoot.querySelector(".input"); + expect(inputDiv.hasAttribute("hidden")).to.be.false; + // Hide input element.hideinput = true; await element.updateComplete; - - inputDiv = element.shadowRoot.querySelector('.input'); - expect(inputDiv.hasAttribute('hidden')).to.be.true; + + inputDiv = element.shadowRoot.querySelector(".input"); + expect(inputDiv.hasAttribute("hidden")).to.be.true; await expect(element).shadowDom.to.be.accessible(); }); }); @@ -201,41 +205,44 @@ describe("barcode-reader test", () => { it("should setup video and canvas elements", async () => { const video = element.shadowRoot.querySelector("video"); const canvas = element.shadowRoot.querySelector("canvas"); - - expect(video.hasAttribute('muted')).to.be.true; - expect(video.hasAttribute('autoplay')).to.be.true; - expect(video.hasAttribute('playsinline')).to.be.true; - expect(canvas.style.display).to.equal('none'); + + expect(video.hasAttribute("muted")).to.be.true; + expect(video.hasAttribute("autoplay")).to.be.true; + expect(video.hasAttribute("playsinline")).to.be.true; + expect(canvas.style.display).to.equal("none"); }); it("should handle scan button click", async () => { - await new Promise(resolve => setTimeout(resolve, 100)); // Wait for ZXing to load - - const scanButton = element.shadowRoot.querySelector('.go'); + await new Promise((resolve) => setTimeout(resolve, 100)); // Wait for ZXing to load + + const scanButton = element.shadowRoot.querySelector(".go"); expect(scanButton).to.exist; - + // Simulate button click let clicked = false; - scanButton.onclick = () => { clicked = true; }; + scanButton.onclick = () => { + clicked = true; + }; scanButton.click(); - + expect(clicked).to.be.true; }); it("should handle camera initialization", async () => { - const renderButton = element.shadowRoot.querySelector('simple-icon-button'); + const renderButton = + element.shadowRoot.querySelector("simple-icon-button"); expect(renderButton).to.exist; - expect(renderButton.getAttribute('icon')).to.equal('image:camera-alt'); + expect(renderButton.getAttribute("icon")).to.equal("image:camera-alt"); }); }); describe("Video stream and device management", () => { it("should enumerate video devices", async () => { - await new Promise(resolve => setTimeout(resolve, 100)); - - const select = element.shadowRoot.querySelector('select'); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const select = element.shadowRoot.querySelector("select"); expect(select).to.exist; - + // The mock should populate options setTimeout(() => { expect(select.children.length).to.be.greaterThan(0); @@ -243,34 +250,37 @@ describe("barcode-reader test", () => { }); it("should handle video stream toggle", async () => { - await new Promise(resolve => setTimeout(resolve, 100)); - - const hiddenDiv = element.shadowRoot.querySelector('.hidden'); - const renderButton = element.shadowRoot.querySelector('simple-icon-button'); - - expect(hiddenDiv.style.display).to.equal(''); - + await new Promise((resolve) => setTimeout(resolve, 100)); + + const hiddenDiv = element.shadowRoot.querySelector(".hidden"); + const renderButton = + element.shadowRoot.querySelector("simple-icon-button"); + + expect(hiddenDiv.style.display).to.equal(""); + // Simulate button click to show video renderButton.click(); - await new Promise(resolve => setTimeout(resolve, 150)); - + await new Promise((resolve) => setTimeout(resolve, 150)); + // Video should be shown after click and delay }); it("should handle device detection for mobile vs PC", async () => { // Test PC detection (default mock) - expect(globalThis.navigator.userAgent).to.include('Windows'); - + expect(globalThis.navigator.userAgent).to.include("Windows"); + // Test mobile detection by changing user agent - Object.defineProperty(globalThis.navigator, 'userAgent', { - get: () => 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)', - configurable: true + Object.defineProperty(globalThis.navigator, "userAgent", { + get: () => "Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X)", + configurable: true, }); - + // Create new element to test mobile detection - const mobileElement = await fixture(html``); + const mobileElement = await fixture( + html``, + ); await mobileElement.updateComplete; - + expect(mobileElement).to.exist; }); }); @@ -279,30 +289,30 @@ describe("barcode-reader test", () => { it("should remain accessible when input is hidden", async () => { element.hideinput = true; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); it("should remain accessible with different scale values", async () => { element.scale = 50; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); it("should maintain proper ARIA labeling", () => { - const button = element.shadowRoot.querySelector('simple-icon-button'); - const label = element.shadowRoot.querySelector('#label'); - - expect(button.getAttribute('aria-labelledby')).to.equal('label'); - expect(label.id).to.equal('label'); - expect(label.textContent).to.equal('Initialize'); + const button = element.shadowRoot.querySelector("simple-icon-button"); + const label = element.shadowRoot.querySelector("#label"); + + expect(button.getAttribute("aria-labelledby")).to.equal("label"); + expect(label.id).to.equal("label"); + expect(label.textContent).to.equal("Initialize"); }); it("should remain accessible during scanning state", async () => { element.value = "scanned-code-123"; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); }); @@ -311,47 +321,47 @@ describe("barcode-reader test", () => { it("should handle undefined scale gracefully", async () => { element.scale = undefined; await element.updateComplete; - + const video = element.shadowRoot.querySelector("video"); - expect(video.getAttribute('width')).to.equal('undefined%'); + expect(video.getAttribute("width")).to.equal("undefined%"); await expect(element).shadowDom.to.be.accessible(); }); it("should handle null value gracefully", async () => { element.value = null; await element.updateComplete; - + expect(element.value).to.be.null; const input = element.shadowRoot.querySelector('input[type="text"]'); - expect(input.value).to.equal(''); + expect(input.value).to.equal(""); await expect(element).shadowDom.to.be.accessible(); }); it("should handle very long barcode values", async () => { - const longValue = 'A'.repeat(1000); + const longValue = "A".repeat(1000); element.value = longValue; await element.updateComplete; - + expect(element.value).to.equal(longValue); await expect(element).shadowDom.to.be.accessible(); }); it("should handle special characters in barcode values", async () => { - const specialChars = '!@#$%^&*()_+-=[]{}|;\':",./<>?'; + const specialChars = "!@#$%^&*()_+-=[]{}|;':\",./<>?"; element.value = specialChars; await element.updateComplete; - + expect(element.value).to.equal(specialChars); await expect(element).shadowDom.to.be.accessible(); }); it("should handle extreme scale values", async () => { const extremeScales = [0, -50, 1000, Number.MAX_SAFE_INTEGER]; - + for (const scale of extremeScales) { element.scale = scale; await element.updateComplete; - + expect(element.scale).to.equal(scale); await expect(element).shadowDom.to.be.accessible(); } @@ -359,10 +369,12 @@ describe("barcode-reader test", () => { it("should handle missing ZXing library gracefully", async () => { delete globalThis.ZXing; - - const testElement = await fixture(html``); + + const testElement = await fixture( + html``, + ); await testElement.updateComplete; - + // Should not throw errors even without ZXing expect(testElement).to.exist; await expect(testElement).shadowDom.to.be.accessible(); @@ -371,12 +383,14 @@ describe("barcode-reader test", () => { it("should handle media device errors gracefully", async () => { // Mock getUserMedia to reject globalThis.navigator.mediaDevices.getUserMedia = () => { - return Promise.reject(new Error('Camera not available')); + return Promise.reject(new Error("Camera not available")); }; - - const testElement = await fixture(html``); + + const testElement = await fixture( + html``, + ); await testElement.updateComplete; - + expect(testElement).to.exist; await expect(testElement).shadowDom.to.be.accessible(); }); @@ -385,9 +399,9 @@ describe("barcode-reader test", () => { describe("Lifecycle methods", () => { it("should handle firstUpdated lifecycle", async () => { // firstUpdated should be called automatically - const video = element.shadowRoot.querySelector('video'); - const canvas = element.shadowRoot.querySelector('canvas'); - + const video = element.shadowRoot.querySelector("video"); + const canvas = element.shadowRoot.querySelector("canvas"); + expect(video).to.exist; expect(canvas).to.exist; expect(element.__context).to.exist; @@ -396,94 +410,97 @@ describe("barcode-reader test", () => { it("should handle updated lifecycle with value changes", async () => { let eventFired = false; - element.addEventListener('value-changed', () => { + element.addEventListener("value-changed", () => { eventFired = true; }); - - element.value = 'updated-value'; + + element.value = "updated-value"; await element.updateComplete; - + expect(eventFired).to.be.true; }); it("should initialize properly in constructor", () => { - const newElement = new (element.constructor)(); - - expect(newElement.value).to.equal(''); + const newElement = new element.constructor(); + + expect(newElement.value).to.equal(""); expect(newElement.hideinput).to.be.false; }); }); describe("UI behavior and interaction", () => { it("should toggle video display on button click", async () => { - await new Promise(resolve => setTimeout(resolve, 100)); - - const renderButton = element.shadowRoot.querySelector('simple-icon-button'); - const hiddenDiv = element.shadowRoot.querySelector('.hidden'); - + await new Promise((resolve) => setTimeout(resolve, 100)); + + const renderButton = + element.shadowRoot.querySelector("simple-icon-button"); + const hiddenDiv = element.shadowRoot.querySelector(".hidden"); + // Initial state - expect(hiddenDiv.hasAttribute('hidden')).to.be.true; - + expect(hiddenDiv.hasAttribute("hidden")).to.be.true; + // Click to initialize and show renderButton.click(); - await new Promise(resolve => setTimeout(resolve, 150)); - + await new Promise((resolve) => setTimeout(resolve, 150)); + // Should change state after processing expect(renderButton).to.exist; }); it("should update input value when value property changes", async () => { const input = element.shadowRoot.querySelector('input[type="text"]'); - - element.value = 'test-input-sync'; + + element.value = "test-input-sync"; await element.updateComplete; - - expect(input.value).to.equal('test-input-sync'); + + expect(input.value).to.equal("test-input-sync"); }); it("should handle multiple rapid value changes", async () => { - const values = ['val1', 'val2', 'val3', 'val4', 'val5']; - + const values = ["val1", "val2", "val3", "val4", "val5"]; + for (const value of values) { element.value = value; await element.updateComplete; } - - expect(element.value).to.equal('val5'); + + expect(element.value).to.equal("val5"); const input = element.shadowRoot.querySelector('input[type="text"]'); - expect(input.value).to.equal('val5'); + expect(input.value).to.equal("val5"); }); }); describe("Performance considerations", () => { it("should handle multiple instances efficiently", async () => { const startTime = performance.now(); - + const elements = await Promise.all([ fixture(html``), fixture(html``), - fixture(html``) + fixture(html``), ]); - + const endTime = performance.now(); const creationTime = endTime - startTime; - + expect(elements.length).to.equal(3); expect(creationTime).to.be.lessThan(1000); // Should create quickly - - elements.forEach(el => { - expect(el.tagName.toLowerCase()).to.equal('barcode-reader'); + + elements.forEach((el) => { + expect(el.tagName.toLowerCase()).to.equal("barcode-reader"); }); }); it("should cleanup resources properly", async () => { - const testElement = await fixture(html``); - + const testElement = await fixture( + html``, + ); + // Simulate cleanup scenario if (globalThis.stream && globalThis.stream.getTracks) { const tracks = globalThis.stream.getTracks(); - tracks.forEach(track => { - expect(track.stop).to.be.a('function'); + tracks.forEach((track) => { + expect(track.stop).to.be.a("function"); }); } }); @@ -491,48 +508,48 @@ describe("barcode-reader test", () => { describe("CSS styles and theming", () => { it("should apply correct styles to elements", () => { - const canvas = element.shadowRoot.querySelector('canvas'); - const video = element.shadowRoot.querySelector('video'); - + const canvas = element.shadowRoot.querySelector("canvas"); + const video = element.shadowRoot.querySelector("video"); + const canvasStyles = globalThis.getComputedStyle(canvas); const videoStyles = globalThis.getComputedStyle(video); - - expect(canvasStyles.display).to.equal('none'); - expect(videoStyles.borderStyle).to.equal('solid'); + + expect(canvasStyles.display).to.equal("none"); + expect(videoStyles.borderStyle).to.equal("solid"); }); it("should handle hidden attribute styling", async () => { - element.setAttribute('hidden', ''); + element.setAttribute("hidden", ""); await element.updateComplete; - + const elementStyles = globalThis.getComputedStyle(element); - expect(elementStyles.display).to.equal('none'); + expect(elementStyles.display).to.equal("none"); }); }); describe("Custom events and integration", () => { it("should dispatch custom events with proper detail", async () => { let capturedEvent = null; - - element.addEventListener('value-changed', (e) => { + + element.addEventListener("value-changed", (e) => { capturedEvent = e; }); - - element.value = 'event-test-value'; + + element.value = "event-test-value"; await element.updateComplete; - + expect(capturedEvent).to.not.be.null; - expect(capturedEvent.type).to.equal('value-changed'); + expect(capturedEvent.type).to.equal("value-changed"); expect(capturedEvent.detail).to.equal(element); }); it("should handle ES bridge events properly", (done) => { // Test the ES bridge ZXing loaded event - globalThis.addEventListener('es-bridge-zxing-loaded', () => { + globalThis.addEventListener("es-bridge-zxing-loaded", () => { expect(true).to.be.true; // Event was fired done(); }); - + // Trigger the event (already triggered in beforeEach mock) }); }); diff --git a/elements/baseline-build-hax/.gitignore b/elements/baseline-build-hax/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/baseline-build-hax/.gitignore +++ b/elements/baseline-build-hax/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/baseline-build-hax/custom-elements.json b/elements/baseline-build-hax/custom-elements.json deleted file mode 100755 index 616ccb6acb..0000000000 --- a/elements/baseline-build-hax/custom-elements.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "baseline-build-hax.js", - "declarations": [], - "exports": [] - } - ] -} diff --git a/elements/baseline-build-hax/demo/index.html b/elements/baseline-build-hax/demo/index.html index 507507bafe..3f4b6a614e 100644 --- a/elements/baseline-build-hax/demo/index.html +++ b/elements/baseline-build-hax/demo/index.html @@ -9,10 +9,10 @@ - +
diff --git a/elements/baseline-build-hax/index.html b/elements/baseline-build-hax/index.html index 084d87740d..5f70e13895 100755 --- a/elements/baseline-build-hax/index.html +++ b/elements/baseline-build-hax/index.html @@ -4,10 +4,10 @@ baseline-build-hax documentation - - + + - + diff --git a/elements/baseline-build-hax/package.json b/elements/baseline-build-hax/package.json old mode 100755 new mode 100644 index 666910a8f0..a356356525 --- a/elements/baseline-build-hax/package.json +++ b/elements/baseline-build-hax/package.json @@ -74,8 +74,6 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" diff --git a/elements/baseline-build-hax/test/baseline-build-hax.test.js b/elements/baseline-build-hax/test/baseline-build-hax.test.js index 2cc0f007ff..ed8fdd3407 100644 --- a/elements/baseline-build-hax/test/baseline-build-hax.test.js +++ b/elements/baseline-build-hax/test/baseline-build-hax.test.js @@ -189,74 +189,74 @@ describe("baseline-build-hax module test", () => { it("should have required HAXStore methods", () => { const store = globalThis.HAXStore; - expect(store).to.have.property('requestAvailability'); - expect(typeof store.requestAvailability).to.equal('function'); + expect(store).to.have.property("requestAvailability"); + expect(typeof store.requestAvailability).to.equal("function"); }); }); describe("Component registry validation", () => { it("should have all components registered in custom elements registry", () => { const expectedComponents = [ - 'wysiwyg-hax', - 'cms-hax', - 'hax-body', - 'hax-tray', - 'hax-app-picker', - 'hax-app', - 'hax-toolbar', - 'a11y-gif-player', - 'citation-element', - 'image-compare-slider', - 'license-element', - 'lrn-math', - 'lrn-table', - 'lrn-vocab', - 'oer-schema', - 'media-image', - 'meme-maker', - 'multiple-choice', - 'person-testimonial', - 'place-holder', - 'q-r', - 'self-check', - 'stop-note', - 'video-player', - 'wikipedia-query', - 'grid-plate' + "wysiwyg-hax", + "cms-hax", + "hax-body", + "hax-tray", + "hax-app-picker", + "hax-app", + "hax-toolbar", + "a11y-gif-player", + "citation-element", + "image-compare-slider", + "license-element", + "lrn-math", + "lrn-table", + "lrn-vocab", + "oer-schema", + "media-image", + "meme-maker", + "multiple-choice", + "person-testimonial", + "place-holder", + "q-r", + "self-check", + "stop-note", + "video-player", + "wikipedia-query", + "grid-plate", ]; - expectedComponents.forEach(tagName => { + expectedComponents.forEach((tagName) => { const constructor = globalThis.customElements.get(tagName); expect(constructor).to.exist; - expect(typeof constructor).to.equal('function'); + expect(typeof constructor).to.equal("function"); }); }); it("should create instances of all registered components", () => { const componentTagNames = [ - 'wysiwyg-hax', - 'cms-hax', - 'hax-body', - 'a11y-gif-player', - 'citation-element', - 'license-element', - 'media-image', - 'meme-maker', - 'place-holder', - 'q-r', - 'self-check', - 'stop-note', - 'video-player', - 'grid-plate' + "wysiwyg-hax", + "cms-hax", + "hax-body", + "a11y-gif-player", + "citation-element", + "license-element", + "media-image", + "meme-maker", + "place-holder", + "q-r", + "self-check", + "stop-note", + "video-player", + "grid-plate", ]; - componentTagNames.forEach(tagName => { + componentTagNames.forEach((tagName) => { const element = globalThis.document.createElement(tagName); expect(element).to.exist; expect(element.tagName.toLowerCase()).to.equal(tagName); - + // Verify it's a proper custom element - expect(element.constructor.name).to.not.equal('HTMLUnknownElement'); + expect(element.constructor.name).to.not.equal("HTMLUnknownElement"); }); }); }); @@ -266,21 +266,21 @@ describe("baseline-build-hax module test", () => { // This test verifies that the import completed successfully // which means all components loaded without major issues const startTime = performance.now(); - + // Test that we can create multiple elements quickly const elements = [ - globalThis.document.createElement('hax-body'), - globalThis.document.createElement('media-image'), - globalThis.document.createElement('video-player') + globalThis.document.createElement("hax-body"), + globalThis.document.createElement("media-image"), + globalThis.document.createElement("video-player"), ]; - + const endTime = performance.now(); const creationTime = endTime - startTime; - - elements.forEach(element => { + + elements.forEach((element) => { expect(element).to.exist; }); - + // Element creation should be fast expect(creationTime).to.be.lessThan(100); }); @@ -289,24 +289,24 @@ describe("baseline-build-hax module test", () => { describe("HAX ecosystem integration", () => { it("should provide components that integrate with HAX", () => { // Test that components have HAX-related methods where expected - const haxBodyElement = globalThis.document.createElement('hax-body'); - + const haxBodyElement = globalThis.document.createElement("hax-body"); + // HAX body should have core HAX functionality expect(haxBodyElement).to.exist; - expect(haxBodyElement.tagName.toLowerCase()).to.equal('hax-body'); + expect(haxBodyElement.tagName.toLowerCase()).to.equal("hax-body"); }); it("should provide educational components", () => { // Test key educational components are available const educationalComponents = [ - 'multiple-choice', - 'self-check', - 'lrn-math', - 'lrn-vocab', - 'stop-note' + "multiple-choice", + "self-check", + "lrn-math", + "lrn-vocab", + "stop-note", ]; - educationalComponents.forEach(tagName => { + educationalComponents.forEach((tagName) => { const element = globalThis.document.createElement(tagName); expect(element).to.exist; expect(element.tagName.toLowerCase()).to.equal(tagName); @@ -316,13 +316,13 @@ describe("baseline-build-hax module test", () => { it("should provide media components", () => { // Test key media components are available const mediaComponents = [ - 'video-player', - 'media-image', - 'a11y-gif-player', - 'meme-maker' + "video-player", + "media-image", + "a11y-gif-player", + "meme-maker", ]; - mediaComponents.forEach(tagName => { + mediaComponents.forEach((tagName) => { const element = globalThis.document.createElement(tagName); expect(element).to.exist; expect(element.tagName.toLowerCase()).to.equal(tagName); @@ -333,41 +333,41 @@ describe("baseline-build-hax module test", () => { describe("Bundle completeness", () => { it("should include core HAX editing functionality", () => { const coreComponents = [ - 'hax-body', - 'hax-tray', - 'hax-toolbar', - 'hax-app-picker', - 'wysiwyg-hax', - 'cms-hax' + "hax-body", + "hax-tray", + "hax-toolbar", + "hax-app-picker", + "wysiwyg-hax", + "cms-hax", ]; - coreComponents.forEach(tagName => { + coreComponents.forEach((tagName) => { expect(globalThis.customElements.get(tagName)).to.exist; }); }); it("should include content authoring components", () => { const contentComponents = [ - 'citation-element', - 'license-element', - 'oer-schema', - 'place-holder' + "citation-element", + "license-element", + "oer-schema", + "place-holder", ]; - contentComponents.forEach(tagName => { + contentComponents.forEach((tagName) => { expect(globalThis.customElements.get(tagName)).to.exist; }); }); it("should include interactive elements", () => { const interactiveComponents = [ - 'multiple-choice', - 'self-check', - 'q-r', - 'wikipedia-query' + "multiple-choice", + "self-check", + "q-r", + "wikipedia-query", ]; - interactiveComponents.forEach(tagName => { + interactiveComponents.forEach((tagName) => { expect(globalThis.customElements.get(tagName)).to.exist; }); }); @@ -376,10 +376,10 @@ describe("baseline-build-hax module test", () => { describe("Accessibility compliance", () => { it("should include accessibility-focused components", () => { const a11yComponents = [ - 'a11y-gif-player' // Specifically named for accessibility + "a11y-gif-player", // Specifically named for accessibility ]; - a11yComponents.forEach(tagName => { + a11yComponents.forEach((tagName) => { const element = globalThis.document.createElement(tagName); expect(element).to.exist; expect(element.tagName.toLowerCase()).to.equal(tagName); @@ -388,18 +388,14 @@ describe("baseline-build-hax module test", () => { it("should create accessible elements by default", () => { // Test that key components don't have obvious accessibility issues - const componentsToTest = [ - 'hax-body', - 'media-image', - 'stop-note' - ]; + const componentsToTest = ["hax-body", "media-image", "stop-note"]; - componentsToTest.forEach(tagName => { + componentsToTest.forEach((tagName) => { const element = globalThis.document.createElement(tagName); expect(element).to.exist; - + // Elements should not have role="none" or other problematic defaults - expect(element.getAttribute('role')).to.not.equal('none'); + expect(element.getAttribute("role")).to.not.equal("none"); }); }); }); @@ -407,24 +403,20 @@ describe("baseline-build-hax module test", () => { describe("Educational content standards", () => { it("should include OER-compliant components", () => { const oerComponents = [ - 'oer-schema', - 'license-element', - 'citation-element' + "oer-schema", + "license-element", + "citation-element", ]; - oerComponents.forEach(tagName => { + oerComponents.forEach((tagName) => { expect(globalThis.customElements.get(tagName)).to.exist; }); }); it("should include learning resource components", () => { - const learningComponents = [ - 'lrn-math', - 'lrn-table', - 'lrn-vocab' - ]; + const learningComponents = ["lrn-math", "lrn-table", "lrn-vocab"]; - learningComponents.forEach(tagName => { + learningComponents.forEach((tagName) => { expect(globalThis.customElements.get(tagName)).to.exist; }); }); diff --git a/elements/beaker-broker/.gitignore b/elements/beaker-broker/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/beaker-broker/.gitignore +++ b/elements/beaker-broker/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/beaker-broker/custom-elements.json b/elements/beaker-broker/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/beaker-broker/demo/index.html b/elements/beaker-broker/demo/index.html index c37041bcc9..b2448d030e 100644 --- a/elements/beaker-broker/demo/index.html +++ b/elements/beaker-broker/demo/index.html @@ -7,10 +7,10 @@ - +
diff --git a/elements/beaker-broker/index.html b/elements/beaker-broker/index.html index b347fe91ce..f61e9963e4 100755 --- a/elements/beaker-broker/index.html +++ b/elements/beaker-broker/index.html @@ -4,10 +4,10 @@ beaker-broker documentation - - + + - + diff --git a/elements/beaker-broker/package.json b/elements/beaker-broker/package.json old mode 100755 new mode 100644 index c6bed85d19..efeb5cdda9 --- a/elements/beaker-broker/package.json +++ b/elements/beaker-broker/package.json @@ -47,10 +47,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/bootstrap-theme/.gitignore b/elements/bootstrap-theme/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/bootstrap-theme/.gitignore +++ b/elements/bootstrap-theme/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/bootstrap-theme/bootstrap-theme.js b/elements/bootstrap-theme/bootstrap-theme.js index 439a9977f8..09e5d8e022 100644 --- a/elements/bootstrap-theme/bootstrap-theme.js +++ b/elements/bootstrap-theme/bootstrap-theme.js @@ -504,10 +504,18 @@ class BootstrapTheme extends HAXCMSThemeParts( return html`
-
+
${this.__siteImage - ? html`${this.__siteTitle} site logo` + ? html`${this.__siteTitle} site logo` : ``}

${this.__siteTitle}

@@ -550,8 +558,10 @@ class BootstrapTheme extends HAXCMSThemeParts(
diff --git a/elements/bootstrap-theme/custom-elements.json b/elements/bootstrap-theme/custom-elements.json deleted file mode 100644 index fbfa9ea2a4..0000000000 --- a/elements/bootstrap-theme/custom-elements.json +++ /dev/null @@ -1,979 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "bootstrap-theme.js", - "declarations": [ - { - "kind": "class", - "description": "`bootstrap-theme`\n`Hax bootstrap themeing`", - "name": "BootstrapTheme", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Convention we use", - "readonly": true - }, - { - "kind": "method", - "name": "searchChanged", - "parameters": [ - { - "name": "evt" - } - ] - }, - { - "kind": "method", - "name": "searchItemSelected", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "_generateBootstrapLink" - }, - { - "kind": "method", - "name": "_loadScripts" - }, - { - "kind": "method", - "name": "_bootstrapLoaded", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "_loadBootstrap" - }, - { - "kind": "method", - "name": "_jqueryLoaded", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "getBasePath", - "parameters": [ - { - "name": "url" - } - ] - }, - { - "kind": "field", - "name": "autoScroll", - "type": { - "text": "boolean" - }, - "default": "true" - }, - { - "kind": "field", - "name": "menuOpen", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "true", - "attribute": "menu-open", - "reflects": true - }, - { - "kind": "field", - "name": "_bootstrapPath", - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "field", - "name": "_themeElements", - "type": { - "text": "array" - }, - "default": "[]" - }, - { - "kind": "field", - "name": "colorTheme", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"0\"", - "attribute": "color-theme", - "reflects": true, - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "field", - "name": "searchTerm", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "searchTerm" - }, - { - "kind": "field", - "name": "__siteTitle", - "type": { - "text": "string" - }, - "default": "\"\"" - }, - { - "kind": "field", - "name": "___pageTitle", - "type": { - "text": "string" - }, - "default": "\"\"" - }, - { - "kind": "field", - "name": "__siteImage", - "type": { - "text": "string" - }, - "default": "\"\"" - }, - { - "kind": "field", - "name": "__disposer" - }, - { - "kind": "field", - "name": "styles", - "static": true, - "readonly": true, - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "method", - "name": "BootstrapUserStylesMenu", - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "field", - "name": "properties", - "static": true, - "readonly": true, - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "method", - "name": "checkUserStylesMenuOpen", - "parameters": [ - { - "name": "e" - } - ], - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "method", - "name": "toggleUserStylesMenu", - "parameters": [ - { - "name": "e" - } - ], - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "method", - "name": "UserStylesSizeDown", - "parameters": [ - { - "name": "e" - } - ], - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "method", - "name": "UserStylesSizeUp", - "parameters": [ - { - "name": "e" - } - ], - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "method", - "name": "UserStylesFontFamilyChange", - "parameters": [ - { - "name": "e" - } - ], - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "method", - "name": "UserStylesColorThemeChange", - "parameters": [ - { - "name": "e" - } - ], - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "field", - "name": "hideUserStylesMenu", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "true", - "attribute": "hideUserStylesMenu", - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "field", - "name": "fontSize", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "1", - "attribute": "font-size", - "reflects": true, - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "kind": "field", - "name": "fontFamily", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "0", - "attribute": "font-family", - "reflects": true, - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - } - ], - "attributes": [ - { - "name": "searchTerm", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "searchTerm" - }, - { - "name": "menu-open", - "type": { - "text": "boolean" - }, - "default": "true", - "fieldName": "menuOpen" - }, - { - "name": "color-theme", - "type": { - "text": "string" - }, - "default": "\"0\"", - "fieldName": "colorTheme", - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "name": "hideUserStylesMenu", - "type": { - "text": "boolean" - }, - "default": "true", - "fieldName": "hideUserStylesMenu", - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "name": "font-size", - "type": { - "text": "number" - }, - "default": "1", - "fieldName": "fontSize", - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - }, - { - "name": "font-family", - "type": { - "text": "number" - }, - "default": "0", - "fieldName": "fontFamily", - "inheritedFrom": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - } - ], - "mixins": [ - { - "name": "HAXCMSThemeParts", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSThemeParts.js" - }, - { - "name": "BootstrapUserStylesMenuMixin", - "package": "@haxtheweb/bootstrap-theme/lib/BootstrapUserStylesMenuMixin.js" - }, - { - "name": "HAXCMSMobileMenuMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSMobileMenu.js" - } - ], - "superclass": { - "name": "HAXCMSLitElementTheme", - "package": "@haxtheweb/haxcms-elements/lib/core/HAXCMSLitElementTheme.js" - }, - "tagName": "bootstrap-theme", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "BootstrapTheme", - "module": "bootstrap-theme.js" - } - }, - { - "kind": "js", - "name": "BootstrapTheme", - "declaration": { - "name": "BootstrapTheme", - "module": "bootstrap-theme.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/BootstrapBreadcrumb.js", - "declarations": [ - { - "kind": "class", - "description": "`bootstrap-breadcrumb`\n`Breadcrumb element for bootstrap theme`", - "name": "BootstrapBreadcrumb", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "getBasePath", - "parameters": [ - { - "name": "url" - } - ] - }, - { - "kind": "method", - "name": "getParentById", - "parameters": [ - { - "name": "parentId" - } - ] - }, - { - "kind": "method", - "name": "addParentToItems", - "parameters": [ - { - "name": "item" - } - ] - }, - { - "kind": "field", - "name": "items", - "privacy": "public", - "type": { - "text": "array" - }, - "default": "[]", - "attribute": "items" - }, - { - "kind": "field", - "name": "_activeItem", - "type": { - "text": "object" - }, - "default": "{}" - }, - { - "kind": "field", - "name": "homeItem", - "privacy": "public", - "type": { - "text": "object" - }, - "default": "{}", - "attribute": "homeItem" - }, - { - "kind": "field", - "name": "_bootstrapPath" - }, - { - "kind": "field", - "name": "__disposer" - }, - { - "kind": "field", - "name": "colorTheme", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "color-theme", - "reflects": true - } - ], - "attributes": [ - { - "name": "items", - "type": { - "text": "array" - }, - "default": "[]", - "fieldName": "items" - }, - { - "name": "homeItem", - "type": { - "text": "object" - }, - "default": "{}", - "fieldName": "homeItem" - }, - { - "name": "color-theme", - "type": { - "text": "string" - }, - "fieldName": "colorTheme" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "bootstrap-breadcrumb", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "BootstrapBreadcrumb", - "module": "lib/BootstrapBreadcrumb.js" - } - }, - { - "kind": "js", - "name": "BootstrapBreadcrumb", - "declaration": { - "name": "BootstrapBreadcrumb", - "module": "lib/BootstrapBreadcrumb.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/BootstrapFooter.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "BootstrapFooter", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "getBasePath", - "parameters": [ - { - "name": "url" - } - ] - }, - { - "kind": "field", - "name": "_bootstrapPath" - }, - { - "kind": "field", - "name": "_forwardItem", - "privacy": "public", - "type": { - "text": "object" - }, - "default": "{}", - "attribute": "_forwardItem" - }, - { - "kind": "field", - "name": "_backwardItem", - "privacy": "public", - "type": { - "text": "object" - }, - "default": "{}", - "attribute": "_backwardItem" - }, - { - "kind": "field", - "name": "_activeItemIndex", - "type": { - "text": "number" - }, - "default": "-1" - }, - { - "kind": "field", - "name": "_routerManifest", - "type": { - "text": "object" - }, - "default": "{}" - }, - { - "kind": "field", - "name": "__disposer" - }, - { - "kind": "field", - "name": "colorTheme", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "color-theme" - } - ], - "attributes": [ - { - "name": "_backwardItem", - "type": { - "text": "object" - }, - "default": "{}", - "fieldName": "_backwardItem" - }, - { - "name": "_forwardItem", - "type": { - "text": "object" - }, - "default": "{}", - "fieldName": "_forwardItem" - }, - { - "name": "color-theme", - "type": { - "text": "string" - }, - "fieldName": "colorTheme" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "BootstrapFooter", - "module": "lib/BootstrapFooter.js" - } - }, - { - "kind": "js", - "name": "BootstrapFooter", - "declaration": { - "name": "BootstrapFooter", - "module": "lib/BootstrapFooter.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/BootstrapSearch.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "BootstrapSearch", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "inputChanged", - "parameters": [ - { - "name": "evt" - } - ] - }, - { - "kind": "method", - "name": "getBasePath", - "parameters": [ - { - "name": "url" - } - ] - }, - { - "kind": "field", - "name": "_bootstrapPath" - }, - { - "kind": "field", - "name": "searchText", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "searchText" - }, - { - "kind": "field", - "name": "colorTheme", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "color-theme", - "reflects": true - } - ], - "events": [ - { - "name": "search-changed", - "type": { - "text": "CustomEvent" - } - } - ], - "attributes": [ - { - "name": "color-theme", - "type": { - "text": "string" - }, - "fieldName": "colorTheme" - }, - { - "name": "searchText", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "searchText" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "BootstrapSearch", - "module": "lib/BootstrapSearch.js" - } - }, - { - "kind": "js", - "name": "BootstrapSearch", - "declaration": { - "name": "BootstrapSearch", - "module": "lib/BootstrapSearch.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/BootstrapUserStylesMenuMixin.js", - "declarations": [ - { - "kind": "mixin", - "description": "", - "name": "BootstrapUserStylesMenuMixin", - "members": [ - { - "kind": "field", - "name": "styles", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "BootstrapUserStylesMenu" - }, - { - "kind": "field", - "name": "properties", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "checkUserStylesMenuOpen", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "toggleUserStylesMenu", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "UserStylesSizeDown", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "UserStylesSizeUp", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "UserStylesFontFamilyChange", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "UserStylesColorThemeChange", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "field", - "name": "hideUserStylesMenu", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "true", - "attribute": "hideUserStylesMenu" - }, - { - "kind": "field", - "name": "fontSize", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "1", - "attribute": "font-size", - "reflects": true - }, - { - "kind": "field", - "name": "fontFamily", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "0", - "attribute": "font-family", - "reflects": true - }, - { - "kind": "field", - "name": "colorTheme", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"0\"", - "attribute": "color-theme", - "reflects": true - }, - { - "kind": "field", - "name": "_bootstrapPath" - } - ], - "attributes": [ - { - "name": "hideUserStylesMenu", - "type": { - "text": "boolean" - }, - "default": "true", - "fieldName": "hideUserStylesMenu" - }, - { - "name": "font-size", - "type": { - "text": "number" - }, - "default": "1", - "fieldName": "fontSize" - }, - { - "name": "font-family", - "type": { - "text": "number" - }, - "default": "0", - "fieldName": "fontFamily" - }, - { - "name": "color-theme", - "type": { - "text": "string" - }, - "default": "\"0\"", - "fieldName": "colorTheme" - } - ], - "parameters": [ - { - "name": "SuperClass" - } - ] - } - ], - "exports": [ - { - "kind": "js", - "name": "BootstrapUserStylesMenuMixin", - "declaration": { - "name": "BootstrapUserStylesMenuMixin", - "module": "lib/BootstrapUserStylesMenuMixin.js" - } - } - ] - } - ] -} diff --git a/elements/bootstrap-theme/demo/index.html b/elements/bootstrap-theme/demo/index.html index 6f2b2ca6ab..a0a4cbc78a 100644 --- a/elements/bootstrap-theme/demo/index.html +++ b/elements/bootstrap-theme/demo/index.html @@ -4,15 +4,14 @@ BootstrapTheme: bootstrap-theme Demo - - +
diff --git a/elements/bootstrap-theme/index.html b/elements/bootstrap-theme/index.html index 4cd145b8b2..d990495dc2 100644 --- a/elements/bootstrap-theme/index.html +++ b/elements/bootstrap-theme/index.html @@ -4,10 +4,10 @@ bootstrap-theme documentation - - + + - + diff --git a/elements/bootstrap-theme/lib/BootstrapBreadcrumb.js b/elements/bootstrap-theme/lib/BootstrapBreadcrumb.js index 55c13fd5fa..07597646ae 100644 --- a/elements/bootstrap-theme/lib/BootstrapBreadcrumb.js +++ b/elements/bootstrap-theme/lib/BootstrapBreadcrumb.js @@ -121,7 +121,7 @@ class BootstrapBreadcrumb extends LitElement { :host([color-theme="2"]) a:hover { color: var(--bootstrap-theme-palenight-color); } - + .visually-hidden { position: absolute !important; width: 1px !important; @@ -199,31 +199,39 @@ class BootstrapBreadcrumb extends LitElement { data-bs-placement="top" title="${this.homeItem.title}" > - - + + Home - ${this.items.map( - (item, index) => { - const isLast = index === this.items.length - 1; - return html` -
  • - ${isLast - ? html`${item.title}` - : html`${item.title}` - } -
  • - `; - } - )} + ${this.items.map((item, index) => { + const isLast = index === this.items.length - 1; + return html` +
  • + ${isLast + ? html`${item.title}` + : html`${item.title}`} +
  • + `; + })}
    diff --git a/elements/bootstrap-theme/lib/BootstrapFooter.js b/elements/bootstrap-theme/lib/BootstrapFooter.js index 58c925f268..a7abd77c35 100644 --- a/elements/bootstrap-theme/lib/BootstrapFooter.js +++ b/elements/bootstrap-theme/lib/BootstrapFooter.js @@ -170,20 +170,38 @@ class BootstrapFooter extends LitElement { return html`
    - ${this._backwardItem && this._backwardItem.slug ? html` - - - ${this._backwardItem.title} - - ` : ''} - ${this._forwardItem && this._forwardItem.slug ? html` - - ${this._forwardItem.title} - - - ` : ''} + ${this._backwardItem && this._backwardItem.slug + ? html` + + + ${this._backwardItem.title} + + ` + : ""} + ${this._forwardItem && this._forwardItem.slug + ? html` + + ${this._forwardItem.title} + + + ` + : ""}
    `; } diff --git a/elements/bootstrap-theme/lib/BootstrapSearch.js b/elements/bootstrap-theme/lib/BootstrapSearch.js index 4a48abc276..797f3ff4c3 100644 --- a/elements/bootstrap-theme/lib/BootstrapSearch.js +++ b/elements/bootstrap-theme/lib/BootstrapSearch.js @@ -56,7 +56,9 @@ class BootstrapSearch extends LitElement { return html`
    - + { globalThis.store = { activeManifestIndex: 0, manifest: { - title: 'Test Site', + title: "Test Site", metadata: { author: { - image: 'https://example.com/author.jpg' - } - } + image: "https://example.com/author.jpg", + }, + }, }, - activeTitle: 'Test Page' + activeTitle: "Test Page", }; - + globalThis.toJS = (value) => value; globalThis.autorun = (callback) => { callback(() => {}); return { dispose: () => {} }; }; - + // Mock ESGlobalBridge globalThis.ESGlobalBridge = { requestAvailability: () => ({ @@ -30,17 +30,17 @@ beforeEach(() => { setTimeout(() => { globalThis.dispatchEvent(new CustomEvent(`es-bridge-${name}-loaded`)); }, 10); - } - }) + }, + }), }; - + // Mock AbsolutePositionStateManager globalThis.AbsolutePositionStateManager = { requestAvailability: () => ({ - scrollTarget: null - }) + scrollTarget: null, + }), }; - + // Mock jQuery and Bootstrap loading globalThis.jQuery = globalThis.$ = {}; globalThis.bootstrap = {}; @@ -48,18 +48,20 @@ beforeEach(() => { aftereEach(() => { // Clean up document modifications - const links = globalThis.document.head.querySelectorAll('link[href*="bootstrap"]'); - links.forEach(link => link.remove()); - + const links = globalThis.document.head.querySelectorAll( + 'link[href*="bootstrap"]', + ); + links.forEach((link) => link.remove()); + // Reset body styles if (globalThis.document.body.style.overflow) { - globalThis.document.body.style.removeProperty('overflow'); + globalThis.document.body.style.removeProperty("overflow"); } }); describe("bootstrap-theme test", () => { let element; - + beforeEach(async () => { element = await fixture(html``); await element.updateComplete; @@ -88,13 +90,13 @@ describe("bootstrap-theme test", () => { }); it("should have required theme structure elements", () => { - const site = element.shadowRoot.querySelector('.site'); - const menuOutline = element.shadowRoot.querySelector('.menu-outline'); - const siteBody = element.shadowRoot.querySelector('.site-body'); - const siteHeader = element.shadowRoot.querySelector('.site-header'); - const mainContent = element.shadowRoot.querySelector('.main-content'); - const footer = element.shadowRoot.querySelector('footer'); - + const site = element.shadowRoot.querySelector(".site"); + const menuOutline = element.shadowRoot.querySelector(".menu-outline"); + const siteBody = element.shadowRoot.querySelector(".site-body"); + const siteHeader = element.shadowRoot.querySelector(".site-header"); + const mainContent = element.shadowRoot.querySelector(".main-content"); + const footer = element.shadowRoot.querySelector("footer"); + expect(site).to.exist; expect(menuOutline).to.exist; expect(siteBody).to.exist; @@ -104,10 +106,12 @@ describe("bootstrap-theme test", () => { }); it("should include Bootstrap components", () => { - const breadcrumb = element.shadowRoot.querySelector('bootstrap-breadcrumb'); - const search = element.shadowRoot.querySelector('bootstrap-search'); - const footer = element.shadowRoot.querySelector('bootstrap-footer'); - + const breadcrumb = element.shadowRoot.querySelector( + "bootstrap-breadcrumb", + ); + const search = element.shadowRoot.querySelector("bootstrap-search"); + const footer = element.shadowRoot.querySelector("bootstrap-footer"); + expect(breadcrumb).to.exist; expect(search).to.exist; expect(footer).to.exist; @@ -120,17 +124,17 @@ describe("bootstrap-theme test", () => { // Test open state element.menuOpen = true; await element.updateComplete; - + expect(element.menuOpen).to.be.true; - expect(element.hasAttribute('menu-open')).to.be.true; + expect(element.hasAttribute("menu-open")).to.be.true; await expect(element).shadowDom.to.be.accessible(); - + // Test closed state element.menuOpen = false; await element.updateComplete; - + expect(element.menuOpen).to.be.false; - expect(element.hasAttribute('menu-open')).to.be.false; + expect(element.hasAttribute("menu-open")).to.be.false; await expect(element).shadowDom.to.be.accessible(); }); }); @@ -138,13 +142,13 @@ describe("bootstrap-theme test", () => { describe("colorTheme property", () => { it("should handle different color themes and maintain accessibility", async () => { const themes = ["0", "1", "2"]; // Light, Dark, Palenight - + for (const theme of themes) { element.colorTheme = theme; await element.updateComplete; - + expect(element.colorTheme).to.equal(theme); - expect(element.getAttribute('color-theme')).to.equal(theme); + expect(element.getAttribute("color-theme")).to.equal(theme); await expect(element).shadowDom.to.be.accessible(); } }); @@ -153,16 +157,16 @@ describe("bootstrap-theme test", () => { // Test dark theme element.colorTheme = "1"; await element.updateComplete; - + const computedStyle = globalThis.getComputedStyle(element); // Dark theme should change background color - expect(element.getAttribute('color-theme')).to.equal('1'); - + expect(element.getAttribute("color-theme")).to.equal("1"); + // Test palenight theme element.colorTheme = "2"; await element.updateComplete; - - expect(element.getAttribute('color-theme')).to.equal('2'); + + expect(element.getAttribute("color-theme")).to.equal("2"); }); }); @@ -172,23 +176,23 @@ describe("bootstrap-theme test", () => { "test search", "complex search query", "special chars: !@#$%", - "" + "", ]; - + for (const term of searchTerms) { element.searchTerm = term; await element.updateComplete; - + expect(element.searchTerm).to.equal(term); - + // Search results should be visible when term is not empty - const siteSearch = element.shadowRoot.querySelector('site-search'); + const siteSearch = element.shadowRoot.querySelector("site-search"); if (term !== "") { - expect(siteSearch.hasAttribute('hidden')).to.be.false; + expect(siteSearch.hasAttribute("hidden")).to.be.false; } else { - expect(siteSearch.hasAttribute('hidden')).to.be.true; + expect(siteSearch.hasAttribute("hidden")).to.be.true; } - + await expect(element).shadowDom.to.be.accessible(); } }); @@ -197,43 +201,45 @@ describe("bootstrap-theme test", () => { describe("Bootstrap integration and styling", () => { it("should load Bootstrap CSS", () => { - expect(element._bootstrapPath).to.include('bootstrap.min.css'); - - const linkElement = element.shadowRoot.querySelector('link[rel="stylesheet"]'); + expect(element._bootstrapPath).to.include("bootstrap.min.css"); + + const linkElement = element.shadowRoot.querySelector( + 'link[rel="stylesheet"]', + ); expect(linkElement).to.exist; - expect(linkElement.getAttribute('href')).to.include('bootstrap.min.css'); + expect(linkElement.getAttribute("href")).to.include("bootstrap.min.css"); }); it("should load Bootstrap and jQuery scripts", (done) => { let jqueryLoaded = false; let bootstrapLoaded = false; - - globalThis.addEventListener('es-bridge-jquery-loaded', () => { + + globalThis.addEventListener("es-bridge-jquery-loaded", () => { jqueryLoaded = true; expect(element._jquery).to.be.true; }); - - globalThis.addEventListener('es-bridge-bootstrap-loaded', () => { + + globalThis.addEventListener("es-bridge-bootstrap-loaded", () => { bootstrapLoaded = true; expect(element._bootstrap).to.be.true; - + if (jqueryLoaded && bootstrapLoaded) { done(); } }); - + // Scripts should be loaded during firstUpdated }); it("should have responsive design classes", async () => { // Test different responsive sizes - const responsiveSizes = ['xs', 'sm', 'md', 'lg']; - + const responsiveSizes = ["xs", "sm", "md", "lg"]; + for (const size of responsiveSizes) { - element.setAttribute('responsive-size', size); + element.setAttribute("responsive-size", size); await element.updateComplete; - - expect(element.getAttribute('responsive-size')).to.equal(size); + + expect(element.getAttribute("responsive-size")).to.equal(size); await expect(element).shadowDom.to.be.accessible(); } }); @@ -243,46 +249,47 @@ describe("bootstrap-theme test", () => { it("should handle search changed events", async () => { const mockEvent = { detail: { - searchText: 'test search query' - } + searchText: "test search query", + }, }; - + element.searchChanged(mockEvent); await element.updateComplete; - - expect(element.searchTerm).to.equal('test search query'); - + + expect(element.searchTerm).to.equal("test search query"); + // Content container should be hidden when searching - const contentContainer = element.shadowRoot.querySelector('#contentcontainer'); - expect(contentContainer.hasAttribute('hidden')).to.be.true; + const contentContainer = + element.shadowRoot.querySelector("#contentcontainer"); + expect(contentContainer.hasAttribute("hidden")).to.be.true; }); it("should clear search when empty", async () => { // Set initial search term - element.searchTerm = 'initial search'; + element.searchTerm = "initial search"; await element.updateComplete; - + const mockEvent = { detail: { - searchText: '' - } + searchText: "", + }, }; - + element.searchChanged(mockEvent); await element.updateComplete; - - expect(element.searchTerm).to.equal(''); + + expect(element.searchTerm).to.equal(""); }); it("should handle search item selection", async () => { // Set search term - element.searchTerm = 'test search'; + element.searchTerm = "test search"; await element.updateComplete; - + element.searchItemSelected({}); await element.updateComplete; - - expect(element.searchTerm).to.equal(''); + + expect(element.searchTerm).to.equal(""); }); }); @@ -290,78 +297,83 @@ describe("bootstrap-theme test", () => { it("should toggle menu visibility", async () => { // Menu should start open expect(element.menuOpen).to.be.true; - expect(element.hasAttribute('menu-open')).to.be.true; - + expect(element.hasAttribute("menu-open")).to.be.true; + // Close menu element.menuOpen = false; await element.updateComplete; - + expect(element.menuOpen).to.be.false; - expect(element.hasAttribute('menu-open')).to.be.false; - + expect(element.hasAttribute("menu-open")).to.be.false; + // Reopen menu element.menuOpen = true; await element.updateComplete; - + expect(element.menuOpen).to.be.true; - expect(element.hasAttribute('menu-open')).to.be.true; + expect(element.hasAttribute("menu-open")).to.be.true; }); it("should have mobile menu components", () => { // Should have mobile menu button and navigation - const mobileMenuButton = element.shadowRoot.querySelector('#haxcmsmobilemenubutton'); - const mobileMenuNav = element.shadowRoot.querySelector('#haxcmsmobilemenunav'); - + const mobileMenuButton = element.shadowRoot.querySelector( + "#haxcmsmobilemenubutton", + ); + const mobileMenuNav = element.shadowRoot.querySelector( + "#haxcmsmobilemenunav", + ); + // These are created by mixins, so check they're referenced in the template const template = element.shadowRoot.innerHTML; - expect(template).to.include('HAXCMSMobileMenu'); - expect(template).to.include('HAXCMSMobileMenuButton'); + expect(template).to.include("HAXCMSMobileMenu"); + expect(template).to.include("HAXCMSMobileMenuButton"); }); }); describe("Content and layout", () => { it("should display site title and image", async () => { - const siteTitle = element.shadowRoot.querySelector('.site-title h4'); - const siteImage = element.shadowRoot.querySelector('.site-img'); - - expect(siteTitle.textContent).to.equal('Test Site'); - expect(siteImage.src).to.equal('https://example.com/author.jpg'); + const siteTitle = element.shadowRoot.querySelector(".site-title h4"); + const siteImage = element.shadowRoot.querySelector(".site-img"); + + expect(siteTitle.textContent).to.equal("Test Site"); + expect(siteImage.src).to.equal("https://example.com/author.jpg"); }); it("should display page title", () => { - const pageTitle = element.shadowRoot.querySelector('.page-title'); - expect(pageTitle.textContent).to.equal('Test Page'); + const pageTitle = element.shadowRoot.querySelector(".page-title"); + expect(pageTitle.textContent).to.equal("Test Page"); }); it("should have main content slot", () => { - const slot = element.shadowRoot.querySelector('slot#main-content'); + const slot = element.shadowRoot.querySelector("slot#main-content"); expect(slot).to.exist; }); it("should hide/show content based on search state", async () => { - const contentContainer = element.shadowRoot.querySelector('#contentcontainer'); - const siteSearch = element.shadowRoot.querySelector('site-search'); - + const contentContainer = + element.shadowRoot.querySelector("#contentcontainer"); + const siteSearch = element.shadowRoot.querySelector("site-search"); + // No search term - content visible, search hidden - element.searchTerm = ''; + element.searchTerm = ""; await element.updateComplete; - - expect(contentContainer.hasAttribute('hidden')).to.be.false; - expect(siteSearch.hasAttribute('hidden')).to.be.true; - + + expect(contentContainer.hasAttribute("hidden")).to.be.false; + expect(siteSearch.hasAttribute("hidden")).to.be.true; + // With search term - content hidden, search visible - element.searchTerm = 'test'; + element.searchTerm = "test"; await element.updateComplete; - - expect(contentContainer.hasAttribute('hidden')).to.be.true; - expect(siteSearch.hasAttribute('hidden')).to.be.false; + + expect(contentContainer.hasAttribute("hidden")).to.be.true; + expect(siteSearch.hasAttribute("hidden")).to.be.false; }); }); describe("Theme parts and mixins", () => { it("should include user styles menu", () => { const template = element.shadowRoot.innerHTML; - expect(template).to.include('BootstrapUserStylesMenu'); + expect(template).to.include("BootstrapUserStylesMenu"); }); it("should have theme settings", () => { @@ -371,15 +383,15 @@ describe("bootstrap-theme test", () => { it("should set scroll target", async () => { // firstUpdated should set the scroll target - const siteBody = element.shadowRoot.querySelector('.site-body'); + const siteBody = element.shadowRoot.querySelector(".site-body"); expect(element.HAXCMSThemeSettings.scrollTarget).to.equal(siteBody); }); }); describe("Accessibility scenarios", () => { it("should remain accessible with different color themes", async () => { - const themes = ['0', '1', '2']; - + const themes = ["0", "1", "2"]; + for (const theme of themes) { element.colorTheme = theme; await element.updateComplete; @@ -394,67 +406,71 @@ describe("bootstrap-theme test", () => { }); it("should remain accessible during search", async () => { - element.searchTerm = 'accessibility test'; + element.searchTerm = "accessibility test"; await element.updateComplete; await expect(element).shadowDom.to.be.accessible(); }); it("should have proper ARIA roles and structure", () => { const main = element.shadowRoot.querySelector('main[role="main"]'); - const header = element.shadowRoot.querySelector('header'); - const footer = element.shadowRoot.querySelector('footer'); - + const header = element.shadowRoot.querySelector("header"); + const footer = element.shadowRoot.querySelector("footer"); + expect(main).to.exist; expect(header).to.exist; expect(footer).to.exist; }); it("should maintain focus management", () => { - const pageWrapper = element.shadowRoot.querySelector('.page-wrapper'); - expect(pageWrapper.getAttribute('tabindex')).to.equal('0'); + const pageWrapper = element.shadowRoot.querySelector(".page-wrapper"); + expect(pageWrapper.getAttribute("tabindex")).to.equal("0"); }); }); describe("Edge cases and error handling", () => { it("should handle missing site image gracefully", async () => { // Update store to have no image - globalThis.store.manifest.metadata.author.image = ''; - - const newElement = await fixture(html``); + globalThis.store.manifest.metadata.author.image = ""; + + const newElement = await fixture( + html``, + ); await newElement.updateComplete; - - const siteImage = newElement.shadowRoot.querySelector('.site-img'); + + const siteImage = newElement.shadowRoot.querySelector(".site-img"); expect(siteImage).to.not.exist; - + await expect(newElement).shadowDom.to.be.accessible(); }); it("should handle empty site title", async () => { - globalThis.store.manifest.title = ''; - - const newElement = await fixture(html``); + globalThis.store.manifest.title = ""; + + const newElement = await fixture( + html``, + ); await newElement.updateComplete; - - const siteTitle = newElement.shadowRoot.querySelector('.site-title h4'); - expect(siteTitle.textContent).to.equal(''); - + + const siteTitle = newElement.shadowRoot.querySelector(".site-title h4"); + expect(siteTitle.textContent).to.equal(""); + await expect(newElement).shadowDom.to.be.accessible(); }); it("should handle invalid color theme values", async () => { - element.colorTheme = 'invalid'; + element.colorTheme = "invalid"; await element.updateComplete; - - expect(element.colorTheme).to.equal('invalid'); - expect(element.getAttribute('color-theme')).to.equal('invalid'); + + expect(element.colorTheme).to.equal("invalid"); + expect(element.getAttribute("color-theme")).to.equal("invalid"); await expect(element).shadowDom.to.be.accessible(); }); it("should handle long search terms", async () => { - const longSearch = 'a'.repeat(1000); + const longSearch = "a".repeat(1000); element.searchTerm = longSearch; await element.updateComplete; - + expect(element.searchTerm).to.equal(longSearch); await expect(element).shadowDom.to.be.accessible(); }); @@ -463,7 +479,7 @@ describe("bootstrap-theme test", () => { const specialSearch = ''; element.searchTerm = specialSearch; await element.updateComplete; - + expect(element.searchTerm).to.equal(specialSearch); await expect(element).shadowDom.to.be.accessible(); }); @@ -471,32 +487,32 @@ describe("bootstrap-theme test", () => { describe("Lifecycle methods", () => { it("should handle constructor properly", () => { - const newElement = new (element.constructor)(); - + const newElement = new element.constructor(); + expect(newElement.menuOpen).to.be.true; - expect(newElement.colorTheme).to.equal('0'); - expect(newElement.searchTerm).to.equal(''); + expect(newElement.colorTheme).to.equal("0"); + expect(newElement.searchTerm).to.equal(""); expect(newElement.HAXCMSThemeSettings.autoScroll).to.be.true; }); it("should handle firstUpdated lifecycle", async () => { // firstUpdated should set up scroll target and load scripts expect(element.HAXCMSThemeSettings.scrollTarget).to.exist; - expect(globalThis.document.body.style.overflow).to.equal('hidden'); + expect(globalThis.document.body.style.overflow).to.equal("hidden"); }); it("should handle disconnectedCallback", () => { // Mock the Bootstrap link - element._bootstrapLink = globalThis.document.createElement('link'); + element._bootstrapLink = globalThis.document.createElement("link"); globalThis.document.head.appendChild(element._bootstrapLink); - + // Mock disposer functions element.__disposer = [{ dispose: () => {} }]; - + element.disconnectedCallback(); - + // Should clean up resources - expect(globalThis.document.body.style.overflow).to.equal(''); + expect(globalThis.document.body.style.overflow).to.equal(""); }); it("should handle updated lifecycle", async () => { @@ -506,34 +522,35 @@ describe("bootstrap-theme test", () => { updateCalled = true; return originalUpdated(changedProperties); }; - - element.colorTheme = '1'; + + element.colorTheme = "1"; await element.updateComplete; - + expect(updateCalled).to.be.true; }); }); describe("Utility methods", () => { it("should get base path correctly", () => { - const testUrl = 'http://example.com/@haxtheweb/bootstrap-theme/bootstrap-theme.js'; + const testUrl = + "http://example.com/@haxtheweb/bootstrap-theme/bootstrap-theme.js"; const basePath = element.getBasePath(testUrl); - - expect(basePath).to.equal('http://example.com/'); + + expect(basePath).to.equal("http://example.com/"); }); it("should generate Bootstrap link", () => { const link = element._generateBootstrapLink(); - + expect(link).to.exist; - expect(link.getAttribute('rel')).to.equal('stylesheet'); - expect(link.getAttribute('href')).to.include('bootstrap.min.css'); + expect(link.getAttribute("rel")).to.equal("stylesheet"); + expect(link.getAttribute("href")).to.include("bootstrap.min.css"); }); it("should remove old Bootstrap link when generating new one", () => { const firstLink = element._generateBootstrapLink(); const secondLink = element._generateBootstrapLink(); - + expect(firstLink).to.not.equal(secondLink); expect(globalThis.document.head.contains(firstLink)).to.be.false; expect(globalThis.document.head.contains(secondLink)).to.be.true; @@ -544,43 +561,43 @@ describe("bootstrap-theme test", () => { it("should handle complete theme workflow", async () => { // Start with default state expect(element.menuOpen).to.be.true; - expect(element.colorTheme).to.equal('0'); - expect(element.searchTerm).to.equal(''); - + expect(element.colorTheme).to.equal("0"); + expect(element.searchTerm).to.equal(""); + // Change to dark theme - element.colorTheme = '1'; + element.colorTheme = "1"; await element.updateComplete; - + // Open search - element.searchChanged({ detail: { searchText: 'test search' }}); + element.searchChanged({ detail: { searchText: "test search" } }); await element.updateComplete; - - expect(element.searchTerm).to.equal('test search'); - + + expect(element.searchTerm).to.equal("test search"); + // Close menu element.menuOpen = false; await element.updateComplete; - + // Clear search element.searchItemSelected({}); await element.updateComplete; - - expect(element.searchTerm).to.equal(''); + + expect(element.searchTerm).to.equal(""); expect(element.menuOpen).to.be.false; - expect(element.colorTheme).to.equal('1'); - + expect(element.colorTheme).to.equal("1"); + await expect(element).shadowDom.to.be.accessible(); }); it("should work with different responsive sizes", async () => { - const sizes = ['xs', 'sm', 'md', 'lg', 'xl']; - + const sizes = ["xs", "sm", "md", "lg", "xl"]; + for (const size of sizes) { - element.setAttribute('responsive-size', size); + element.setAttribute("responsive-size", size); await element.updateComplete; - + // Menu and content should adapt to size - expect(element.getAttribute('responsive-size')).to.equal(size); + expect(element.getAttribute("responsive-size")).to.equal(size); await expect(element).shadowDom.to.be.accessible(); } }); @@ -589,33 +606,35 @@ describe("bootstrap-theme test", () => { describe("Performance considerations", () => { it("should handle rapid property changes efficiently", async () => { const startTime = performance.now(); - + // Rapid changes for (let i = 0; i < 10; i++) { element.colorTheme = (i % 3).toString(); element.menuOpen = i % 2 === 0; - element.searchTerm = i % 2 === 0 ? `search ${i}` : ''; + element.searchTerm = i % 2 === 0 ? `search ${i}` : ""; await element.updateComplete; } - + const endTime = performance.now(); const totalTime = endTime - startTime; - + expect(totalTime).to.be.lessThan(1000); - expect(element.colorTheme).to.equal('1'); // (9 % 3).toString() + expect(element.colorTheme).to.equal("1"); // (9 % 3).toString() }); it("should cleanup resources on disconnect", () => { // Set up resources to be cleaned - element._bootstrapLink = globalThis.document.createElement('link'); + element._bootstrapLink = globalThis.document.createElement("link"); globalThis.document.head.appendChild(element._bootstrapLink); element.__disposer = [{ dispose: () => {} }]; - - const linkCount = globalThis.document.head.querySelectorAll('link').length; - + + const linkCount = + globalThis.document.head.querySelectorAll("link").length; + element.disconnectedCallback(); - - const newLinkCount = globalThis.document.head.querySelectorAll('link').length; + + const newLinkCount = + globalThis.document.head.querySelectorAll("link").length; expect(newLinkCount).to.be.lessThan(linkCount); }); }); diff --git a/elements/chartist-render/.gitignore b/elements/chartist-render/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/chartist-render/.gitignore +++ b/elements/chartist-render/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/chartist-render/custom-elements.json b/elements/chartist-render/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/chartist-render/demo/csv.html b/elements/chartist-render/demo/csv.html index bf1640d18a..5a98d6fc16 100644 --- a/elements/chartist-render/demo/csv.html +++ b/elements/chartist-render/demo/csv.html @@ -7,7 +7,7 @@ + + +
    diff --git a/elements/citation-element/index.html b/elements/citation-element/index.html index a677b05c6f..82b31fef5f 100755 --- a/elements/citation-element/index.html +++ b/elements/citation-element/index.html @@ -4,10 +4,10 @@ citation-element documentation - - + + - + diff --git a/elements/citation-element/package.json b/elements/citation-element/package.json old mode 100755 new mode 100644 index 581610210b..779cde88e2 --- a/elements/citation-element/package.json +++ b/elements/citation-element/package.json @@ -41,16 +41,13 @@ "@haxtheweb/license-element": "^11.0.5", "@haxtheweb/schema-behaviors": "^11.0.5", "@haxtheweb/utils": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/citation-element/test/citation-element.test.js b/elements/citation-element/test/citation-element.test.js index 2e55c9a0f3..c050240096 100644 --- a/elements/citation-element/test/citation-element.test.js +++ b/elements/citation-element/test/citation-element.test.js @@ -5,46 +5,50 @@ import "../citation-element.js"; // Mock license-element dependency const mockLicenseList = { - 'by': { - name: 'CC BY', - link: 'https://creativecommons.org/licenses/by/4.0/', - image: 'https://licensebuttons.net/l/by/4.0/88x31.png' + by: { + name: "CC BY", + link: "https://creativecommons.org/licenses/by/4.0/", + image: "https://licensebuttons.net/l/by/4.0/88x31.png", }, - 'by-sa': { - name: 'CC BY-SA', - link: 'https://creativecommons.org/licenses/by-sa/4.0/', - image: 'https://licensebuttons.net/l/by-sa/4.0/88x31.png' + "by-sa": { + name: "CC BY-SA", + link: "https://creativecommons.org/licenses/by-sa/4.0/", + image: "https://licensebuttons.net/l/by-sa/4.0/88x31.png", + }, + cc0: { + name: "CC0", + link: "https://creativecommons.org/publicdomain/zero/1.0/", + image: "https://licensebuttons.net/p/zero/1.0/88x31.png", }, - 'cc0': { - name: 'CC0', - link: 'https://creativecommons.org/publicdomain/zero/1.0/', - image: 'https://licensebuttons.net/p/zero/1.0/88x31.png' - } }; // Mock utils dependency -const mockGenerateResourceID = () => 'test-resource-id-' + Date.now(); +const mockGenerateResourceID = () => "test-resource-id-" + Date.now(); describe("citation-element test", () => { let element, sandbox; beforeEach(async () => { sandbox = sinon.createSandbox(); - + // Mock document.head operations - sandbox.stub(document.head, 'appendChild'); - sandbox.stub(document.head, 'removeChild'); - + sandbox.stub(document.head, "appendChild"); + sandbox.stub(document.head, "removeChild"); + // Mock license list - const licenseElementModule = await import('@haxtheweb/license-element/license-element.js'); - sandbox.stub(licenseElementModule, 'licenseList').returns(mockLicenseList); - + const licenseElementModule = await import( + "@haxtheweb/license-element/license-element.js" + ); + sandbox.stub(licenseElementModule, "licenseList").returns(mockLicenseList); + // Mock utils - const utilsModule = await import('@haxtheweb/utils/utils.js'); - sandbox.stub(utilsModule, 'generateResourceID').returns(mockGenerateResourceID()); - + const utilsModule = await import("@haxtheweb/utils/utils.js"); + sandbox + .stub(utilsModule, "generateResourceID") + .returns(mockGenerateResourceID()); + element = await fixture(html` - { it("passes a11y audit with all properties set", async () => { const el = await fixture(html` - { it("passes a11y audit in footnote mode", async () => { const el = await fixture(html` - @@ -98,123 +102,143 @@ describe("citation-element test", () => { describe("Component Structure", () => { it("renders with correct tag name", () => { - expect(element.tagName.toLowerCase()).to.equal('citation-element'); + expect(element.tagName.toLowerCase()).to.equal("citation-element"); }); it("has proper shadow DOM structure", () => { - const cite = element.shadowRoot.querySelector('cite'); + const cite = element.shadowRoot.querySelector("cite"); expect(cite).to.exist; - - const links = element.shadowRoot.querySelectorAll('a'); + + const links = element.shadowRoot.querySelectorAll("a"); expect(links.length).to.be.at.least(1); }); it("includes meta elements for semantic markup", () => { - const metas = element.shadowRoot.querySelectorAll('meta'); + const metas = element.shadowRoot.querySelectorAll("meta"); expect(metas.length).to.be.at.least(2); - - const attributionUrl = element.shadowRoot.querySelector('meta[property="cc:attributionUrl"]'); + + const attributionUrl = element.shadowRoot.querySelector( + 'meta[property="cc:attributionUrl"]', + ); expect(attributionUrl).to.exist; - - const attributionName = element.shadowRoot.querySelector('meta[property="cc:attributionName"]'); + + const attributionName = element.shadowRoot.querySelector( + 'meta[property="cc:attributionName"]', + ); expect(attributionName).to.exist; }); it("includes license meta when license is set", () => { - const licenseMeta = element.shadowRoot.querySelector('meta[rel="cc:license"]'); + const licenseMeta = element.shadowRoot.querySelector( + 'meta[rel="cc:license"]', + ); expect(licenseMeta).to.exist; - expect(licenseMeta.getAttribute('content')).to.include('License:'); + expect(licenseMeta.getAttribute("content")).to.include("License:"); }); it("has correct ARIA attributes and semantics", () => { - const cite = element.shadowRoot.querySelector('cite'); + const cite = element.shadowRoot.querySelector("cite"); expect(cite).to.exist; - - const links = element.shadowRoot.querySelectorAll('a'); - links.forEach(link => { - expect(link.getAttribute('target')).to.equal('_blank'); - expect(link.getAttribute('rel')).to.include('noopener'); + + const links = element.shadowRoot.querySelectorAll("a"); + links.forEach((link) => { + expect(link.getAttribute("target")).to.equal("_blank"); + expect(link.getAttribute("rel")).to.include("noopener"); }); }); }); describe("Property Handling", () => { it("reflects display-method attribute", async () => { - element.displayMethod = 'footnote'; + element.displayMethod = "footnote"; await element.updateComplete; - expect(element.getAttribute('display-method')).to.equal('footnote'); + expect(element.getAttribute("display-method")).to.equal("footnote"); }); it("handles title property correctly", async () => { - element.title = 'New Title'; + element.title = "New Title"; await element.updateComplete; - - const titleLink = element.shadowRoot.querySelector('a'); - expect(titleLink.textContent).to.equal('New Title'); - - const titleMeta = element.shadowRoot.querySelector('meta[property="cc:attributionName"]'); - expect(titleMeta.getAttribute('content')).to.equal('New Title'); + + const titleLink = element.shadowRoot.querySelector("a"); + expect(titleLink.textContent).to.equal("New Title"); + + const titleMeta = element.shadowRoot.querySelector( + 'meta[property="cc:attributionName"]', + ); + expect(titleMeta.getAttribute("content")).to.equal("New Title"); }); it("handles creator property correctly", async () => { - element.creator = 'Jane Doe'; + element.creator = "Jane Doe"; await element.updateComplete; - - const cite = element.shadowRoot.querySelector('cite'); - expect(cite.textContent).to.include('Jane Doe'); + + const cite = element.shadowRoot.querySelector("cite"); + expect(cite.textContent).to.include("Jane Doe"); }); it("handles source property correctly", async () => { - element.source = 'https://newexample.com'; + element.source = "https://newexample.com"; await element.updateComplete; - - const sourceLink = element.shadowRoot.querySelector('a'); - expect(sourceLink.getAttribute('href')).to.equal('https://newexample.com'); - - const sourceMeta = element.shadowRoot.querySelector('meta[property="cc:attributionUrl"]'); - expect(sourceMeta.getAttribute('content')).to.equal('https://newexample.com'); + + const sourceLink = element.shadowRoot.querySelector("a"); + expect(sourceLink.getAttribute("href")).to.equal( + "https://newexample.com", + ); + + const sourceMeta = element.shadowRoot.querySelector( + 'meta[property="cc:attributionUrl"]', + ); + expect(sourceMeta.getAttribute("content")).to.equal( + "https://newexample.com", + ); }); it("handles date property correctly", async () => { - element.date = '2024-03-01'; + element.date = "2024-03-01"; await element.updateComplete; - - const cite = element.shadowRoot.querySelector('cite'); - expect(cite.textContent).to.include('2024-03-01'); + + const cite = element.shadowRoot.querySelector("cite"); + expect(cite.textContent).to.include("2024-03-01"); }); it("handles license property and updates related fields", async () => { - element.license = 'by-sa'; + element.license = "by-sa"; await element.updateComplete; - - expect(element.licenseName).to.equal('CC BY-SA'); - expect(element.licenseLink).to.equal('https://creativecommons.org/licenses/by-sa/4.0/'); - expect(element.licenseImage).to.equal('https://licensebuttons.net/l/by-sa/4.0/88x31.png'); + + expect(element.licenseName).to.equal("CC BY-SA"); + expect(element.licenseLink).to.equal( + "https://creativecommons.org/licenses/by-sa/4.0/", + ); + expect(element.licenseImage).to.equal( + "https://licensebuttons.net/l/by-sa/4.0/88x31.png", + ); }); it("handles scope property changes", async () => { - element.scope = 'parent'; + element.scope = "parent"; await element.updateComplete; - expect(element.scope).to.equal('parent'); + expect(element.scope).to.equal("parent"); }); it("validates license-name attribute reflection", async () => { - element.licenseName = 'Custom License'; + element.licenseName = "Custom License"; await element.updateComplete; - expect(element.getAttribute('license-name')).to.equal('Custom License'); + expect(element.getAttribute("license-name")).to.equal("Custom License"); }); it("validates license-link attribute reflection", async () => { - element.licenseLink = 'https://custom-license.com'; + element.licenseLink = "https://custom-license.com"; await element.updateComplete; - expect(element.getAttribute('license-link')).to.equal('https://custom-license.com'); + expect(element.getAttribute("license-link")).to.equal( + "https://custom-license.com", + ); }); }); describe("Citation Rendering", () => { it("renders complete citation with all elements", async () => { const el = await fixture(html` - { license="by" > `); - - const cite = el.shadowRoot.querySelector('cite'); + + const cite = el.shadowRoot.querySelector("cite"); const text = cite.textContent; - - expect(text).to.include('Complete Work'); - expect(text).to.include('Author Name'); - expect(text).to.include('licensed under'); - expect(text).to.include('2024-01-01'); - expect(text).to.include('Accessed'); + + expect(text).to.include("Complete Work"); + expect(text).to.include("Author Name"); + expect(text).to.include("licensed under"); + expect(text).to.include("2024-01-01"); + expect(text).to.include("Accessed"); }); it("renders citation without license when not provided", async () => { const el = await fixture(html` - `); - - const cite = el.shadowRoot.querySelector('cite'); + + const cite = el.shadowRoot.querySelector("cite"); const text = cite.textContent; - - expect(text).to.include('No License Work'); - expect(text).to.include('Author Name'); - expect(text).to.not.include('licensed under'); - expect(text).to.include('2024-01-01'); + + expect(text).to.include("No License Work"); + expect(text).to.include("Author Name"); + expect(text).to.not.include("licensed under"); + expect(text).to.include("2024-01-01"); }); it("renders license image when available", async () => { - element.license = 'by'; + element.license = "by"; await element.updateComplete; - - const licenseImg = element.shadowRoot.querySelector('img'); + + const licenseImg = element.shadowRoot.querySelector("img"); expect(licenseImg).to.exist; - expect(licenseImg.getAttribute('src')).to.equal('https://licensebuttons.net/l/by/4.0/88x31.png'); - expect(licenseImg.getAttribute('alt')).to.include('CC BY graphic'); - expect(licenseImg.getAttribute('width')).to.equal('44px'); - expect(licenseImg.getAttribute('height')).to.equal('16px'); + expect(licenseImg.getAttribute("src")).to.equal( + "https://licensebuttons.net/l/by/4.0/88x31.png", + ); + expect(licenseImg.getAttribute("alt")).to.include("CC BY graphic"); + expect(licenseImg.getAttribute("width")).to.equal("44px"); + expect(licenseImg.getAttribute("height")).to.equal("16px"); }); it("hides license image when not available", async () => { - element.licenseImage = ''; + element.licenseImage = ""; await element.updateComplete; - - const licenseImg = element.shadowRoot.querySelector('img'); - expect(licenseImg.hasAttribute('hidden')).to.be.true; + + const licenseImg = element.shadowRoot.querySelector("img"); + expect(licenseImg.hasAttribute("hidden")).to.be.true; }); it("renders proper link attributes for accessibility", () => { - const links = element.shadowRoot.querySelectorAll('a'); - links.forEach(link => { - expect(link.getAttribute('target')).to.equal('_blank'); - expect(link.getAttribute('rel')).to.include('noopener'); - expect(link.getAttribute('rel')).to.include('noreferrer'); + const links = element.shadowRoot.querySelectorAll("a"); + links.forEach((link) => { + expect(link.getAttribute("target")).to.equal("_blank"); + expect(link.getAttribute("rel")).to.include("noopener"); + expect(link.getAttribute("rel")).to.include("noreferrer"); }); }); }); @@ -287,43 +313,43 @@ describe("citation-element test", () => { const el = await fixture(html` `); - + const styles = getComputedStyle(el); - expect(styles.display).to.not.equal('none'); - expect(styles.visibility).to.not.equal('hidden'); + expect(styles.display).to.not.equal("none"); + expect(styles.visibility).to.not.equal("hidden"); }); it("applies footnote styling when display-method is footnote", async () => { const el = await fixture(html` - `); - - expect(el.getAttribute('display-method')).to.equal('footnote'); + + expect(el.getAttribute("display-method")).to.equal("footnote"); // Note: visibility and opacity would be set by CSS, check attribute presence }); it("applies popup styling when display-method is popup", async () => { const el = await fixture(html` - `); - - expect(el.getAttribute('display-method')).to.equal('popup'); + + expect(el.getAttribute("display-method")).to.equal("popup"); }); it("updates display method dynamically", async () => { - element.displayMethod = 'popup'; + element.displayMethod = "popup"; await element.updateComplete; - expect(element.getAttribute('display-method')).to.equal('popup'); - - element.displayMethod = 'footnote'; + expect(element.getAttribute("display-method")).to.equal("popup"); + + element.displayMethod = "footnote"; await element.updateComplete; - expect(element.getAttribute('display-method')).to.equal('footnote'); + expect(element.getAttribute("display-method")).to.equal("footnote"); }); }); @@ -332,56 +358,68 @@ describe("citation-element test", () => { const container = await fixture(html`
    - +
    `); - - const citation = container.querySelector('citation-element'); - citation._scopeChanged('sibling'); - - expect(citation.relatedResource).to.equal('existing-resource'); + + const citation = container.querySelector("citation-element"); + citation._scopeChanged("sibling"); + + expect(citation.relatedResource).to.equal("existing-resource"); }); it("handles sibling scope without existing resource", async () => { const container = await fixture(html`
    - +
    `); - - const citation = container.querySelector('citation-element'); + + const citation = container.querySelector("citation-element"); const sibling = citation.previousElementSibling; - citation._scopeChanged('sibling'); - - expect(sibling.hasAttribute('resource')).to.be.true; + citation._scopeChanged("sibling"); + + expect(sibling.hasAttribute("resource")).to.be.true; expect(citation.relatedResource).to.exist; }); it("handles parent scope with existing resource", async () => { const container = await fixture(html`
    - +
    `); - - const citation = container.querySelector('citation-element'); - citation._scopeChanged('parent'); - - expect(citation.relatedResource).to.equal('parent-resource'); + + const citation = container.querySelector("citation-element"); + citation._scopeChanged("parent"); + + expect(citation.relatedResource).to.equal("parent-resource"); }); it("handles parent scope without existing resource", async () => { const container = await fixture(html`
    - +
    `); - - const citation = container.querySelector('citation-element'); - citation._scopeChanged('parent'); - - expect(container.hasAttribute('resource')).to.be.true; + + const citation = container.querySelector("citation-element"); + citation._scopeChanged("parent"); + + expect(container.hasAttribute("resource")).to.be.true; expect(citation.relatedResource).to.exist; }); @@ -389,62 +427,70 @@ describe("citation-element test", () => { const container = await fixture(html`
    -
    `); - - const citation = container.querySelector('citation-element'); - citation.setAttribute('prefix', 'test-prefix'); - citation._scopeChanged('sibling'); - + + const citation = container.querySelector("citation-element"); + citation.setAttribute("prefix", "test-prefix"); + citation._scopeChanged("sibling"); + const sibling = citation.previousElementSibling; - expect(sibling.getAttribute('prefix')).to.equal('test-prefix'); + expect(sibling.getAttribute("prefix")).to.equal("test-prefix"); }); it("sets prefix on parent element", async () => { const container = await fixture(html`
    -
    `); - - const citation = container.querySelector('citation-element'); - citation.setAttribute('prefix', 'parent-prefix'); - citation._scopeChanged('parent'); - - expect(container.getAttribute('prefix')).to.equal('parent-prefix'); + + const citation = container.querySelector("citation-element"); + citation.setAttribute("prefix", "parent-prefix"); + citation._scopeChanged("parent"); + + expect(container.getAttribute("prefix")).to.equal("parent-prefix"); }); }); describe("License Processing", () => { it("processes known license correctly", async () => { - element._licenseUpdated('by'); - - expect(element.licenseName).to.equal('CC BY'); - expect(element.licenseLink).to.equal('https://creativecommons.org/licenses/by/4.0/'); - expect(element.licenseImage).to.equal('https://licensebuttons.net/l/by/4.0/88x31.png'); + element._licenseUpdated("by"); + + expect(element.licenseName).to.equal("CC BY"); + expect(element.licenseLink).to.equal( + "https://creativecommons.org/licenses/by/4.0/", + ); + expect(element.licenseImage).to.equal( + "https://licensebuttons.net/l/by/4.0/88x31.png", + ); }); it("processes different license types", async () => { - element._licenseUpdated('cc0'); - - expect(element.licenseName).to.equal('CC0'); - expect(element.licenseLink).to.equal('https://creativecommons.org/publicdomain/zero/1.0/'); - expect(element.licenseImage).to.equal('https://licensebuttons.net/p/zero/1.0/88x31.png'); + element._licenseUpdated("cc0"); + + expect(element.licenseName).to.equal("CC0"); + expect(element.licenseLink).to.equal( + "https://creativecommons.org/publicdomain/zero/1.0/", + ); + expect(element.licenseImage).to.equal( + "https://licensebuttons.net/p/zero/1.0/88x31.png", + ); }); it("handles unknown license gracefully", async () => { const originalName = element.licenseName; - element._licenseUpdated('unknown-license'); - + element._licenseUpdated("unknown-license"); + // Should not change if license is unknown expect(element.licenseName).to.equal(originalName); }); @@ -452,58 +498,66 @@ describe("citation-element test", () => { it("handles undefined license", async () => { const originalName = element.licenseName; element._licenseUpdated(undefined); - + expect(element.licenseName).to.equal(originalName); }); }); describe("DOM Link Management", () => { it("creates license link in document head", () => { - element._generateLicenseLink('https://test-source.com'); - + element._generateLicenseLink("https://test-source.com"); + expect(document.head.appendChild.called).to.be.true; const call = document.head.appendChild.getCall(0); const link = call.args[0]; - - expect(link.tagName.toLowerCase()).to.equal('link'); - expect(link.getAttribute('typeof')).to.equal('resource'); - expect(link.getAttribute('rel')).to.equal('license'); - expect(link.getAttribute('src')).to.equal('https://test-source.com'); + + expect(link.tagName.toLowerCase()).to.equal("link"); + expect(link.getAttribute("typeof")).to.equal("resource"); + expect(link.getAttribute("rel")).to.equal("license"); + expect(link.getAttribute("src")).to.equal("https://test-source.com"); }); it("removes existing license link before creating new one", () => { // Create first link - const firstLink = element._generateLicenseLink('https://first-source.com'); + const firstLink = element._generateLicenseLink( + "https://first-source.com", + ); element._licenseLink = firstLink; - + // Create second link - element._generateLicenseLink('https://second-source.com'); - + element._generateLicenseLink("https://second-source.com"); + expect(document.head.removeChild.called).to.be.true; expect(document.head.removeChild.calledWith(firstLink)).to.be.true; }); it("creates about link in document head", () => { - element._generateAboutLink('test-resource', 'https://license-link.com'); - + element._generateAboutLink("test-resource", "https://license-link.com"); + expect(document.head.appendChild.called).to.be.true; const call = document.head.appendChild.getCall(0); const link = call.args[0]; - - expect(link.tagName.toLowerCase()).to.equal('link'); - expect(link.getAttribute('about')).to.equal('test-resource'); - expect(link.getAttribute('property')).to.equal('cc:license'); - expect(link.getAttribute('content')).to.equal('https://license-link.com'); + + expect(link.tagName.toLowerCase()).to.equal("link"); + expect(link.getAttribute("about")).to.equal("test-resource"); + expect(link.getAttribute("property")).to.equal("cc:license"); + expect(link.getAttribute("content")).to.equal("https://license-link.com"); }); it("removes existing about link before creating new one", () => { // Create first link - const firstLink = element._generateAboutLink('first-resource', 'https://first-license.com'); + const firstLink = element._generateAboutLink( + "first-resource", + "https://first-license.com", + ); element._aboutLink = firstLink; - + // Create second link - element._generateAboutLink('second-resource', 'https://second-license.com'); - + element._generateAboutLink( + "second-resource", + "https://second-license.com", + ); + expect(document.head.removeChild.called).to.be.true; expect(document.head.removeChild.calledWith(firstLink)).to.be.true; }); @@ -511,101 +565,101 @@ describe("citation-element test", () => { describe("Property Updates and Lifecycle", () => { it("triggers scope change on scope property update", async () => { - const scopeSpy = sandbox.spy(element, '_scopeChanged'); - - element.scope = 'parent'; + const scopeSpy = sandbox.spy(element, "_scopeChanged"); + + element.scope = "parent"; await element.updateComplete; - - expect(scopeSpy.calledWith('parent')).to.be.true; + + expect(scopeSpy.calledWith("parent")).to.be.true; }); it("triggers license update on license property update", async () => { - const licenseSpy = sandbox.spy(element, '_licenseUpdated'); - - element.license = 'by-sa'; + const licenseSpy = sandbox.spy(element, "_licenseUpdated"); + + element.license = "by-sa"; await element.updateComplete; - - expect(licenseSpy.calledWith('by-sa')).to.be.true; + + expect(licenseSpy.calledWith("by-sa")).to.be.true; }); it("updates about link on relatedResource change", async () => { - const aboutSpy = sandbox.spy(element, '_generateAboutLink'); - - element.relatedResource = 'new-resource'; + const aboutSpy = sandbox.spy(element, "_generateAboutLink"); + + element.relatedResource = "new-resource"; await element.updateComplete; - + expect(aboutSpy.called).to.be.true; }); it("updates about link on licenseLink change", async () => { - const aboutSpy = sandbox.spy(element, '_generateAboutLink'); - - element.licenseLink = 'https://new-license.com'; + const aboutSpy = sandbox.spy(element, "_generateAboutLink"); + + element.licenseLink = "https://new-license.com"; await element.updateComplete; - + expect(aboutSpy.called).to.be.true; }); it("updates license link on source change", async () => { - const licenseLinkSpy = sandbox.spy(element, '_generateLicenseLink'); - - element.source = 'https://new-source.com'; + const licenseLinkSpy = sandbox.spy(element, "_generateLicenseLink"); + + element.source = "https://new-source.com"; await element.updateComplete; - - expect(licenseLinkSpy.calledWith('https://new-source.com')).to.be.true; + + expect(licenseLinkSpy.calledWith("https://new-source.com")).to.be.true; }); it("initializes with correct default values", () => { - expect(element.scope).to.equal('sibling'); - expect(element.source).to.equal(''); + expect(element.scope).to.equal("sibling"); + expect(element.source).to.equal(""); }); }); describe("HAX Integration", () => { it("provides correct haxProperties", () => { const haxProps = element.constructor.haxProperties; - + expect(haxProps.canScale).to.be.false; expect(haxProps.canEditSource).to.be.true; - expect(haxProps.gizmo.title).to.equal('Citation'); - expect(haxProps.gizmo.description).to.include('citation element'); - expect(haxProps.gizmo.icon).to.equal('editor:title'); - expect(haxProps.gizmo.color).to.equal('grey'); + expect(haxProps.gizmo.title).to.equal("Citation"); + expect(haxProps.gizmo.description).to.include("citation element"); + expect(haxProps.gizmo.icon).to.equal("editor:title"); + expect(haxProps.gizmo.color).to.equal("grey"); }); it("includes relevant tags", () => { const haxProps = element.constructor.haxProperties; const tags = haxProps.gizmo.tags; - - expect(tags).to.include('citation'); - expect(tags).to.include('reference'); - expect(tags).to.include('cc0'); - expect(tags).to.include('cc-by'); + + expect(tags).to.include("citation"); + expect(tags).to.include("reference"); + expect(tags).to.include("cc0"); + expect(tags).to.include("cc-by"); }); it("handles citation data correctly", () => { const haxProps = element.constructor.haxProperties; const handles = haxProps.gizmo.handles; - - expect(handles[0].type).to.equal('citation'); - expect(handles[0].source).to.equal('source'); - expect(handles[0].title).to.equal('title'); - expect(handles[0].author).to.equal('creator'); - expect(handles[0].license).to.equal('license'); + + expect(handles[0].type).to.equal("citation"); + expect(handles[0].source).to.equal("source"); + expect(handles[0].title).to.equal("title"); + expect(handles[0].author).to.equal("creator"); + expect(handles[0].license).to.equal("license"); }); it("provides proper configuration options", () => { const haxProps = element.constructor.haxProperties; const configure = haxProps.settings.configure; - - const titleConfig = configure.find(c => c.property === 'title'); - expect(titleConfig.inputMethod).to.equal('textfield'); - - const sourceConfig = configure.find(c => c.property === 'source'); - expect(sourceConfig.validationType).to.equal('url'); - - const scopeConfig = configure.find(c => c.property === 'scope'); - expect(scopeConfig.inputMethod).to.equal('select'); + + const titleConfig = configure.find((c) => c.property === "title"); + expect(titleConfig.inputMethod).to.equal("textfield"); + + const sourceConfig = configure.find((c) => c.property === "source"); + expect(sourceConfig.validationType).to.equal("url"); + + const scopeConfig = configure.find((c) => c.property === "scope"); + expect(scopeConfig.inputMethod).to.equal("select"); expect(scopeConfig.options.sibling).to.exist; expect(scopeConfig.options.parent).to.exist; }); @@ -613,8 +667,8 @@ describe("citation-element test", () => { it("provides demo schema", () => { const haxProps = element.constructor.haxProperties; const demoSchema = haxProps.demoSchema; - - expect(demoSchema[0].tag).to.equal('citation-element'); + + expect(demoSchema[0].tag).to.equal("citation-element"); expect(demoSchema[0].properties.creator).to.exist; expect(demoSchema[0].properties.license).to.exist; expect(demoSchema[0].properties.title).to.exist; @@ -625,130 +679,146 @@ describe("citation-element test", () => { describe("Edge Cases and Error Handling", () => { it("handles empty properties gracefully", async () => { const el = await fixture(html``); - + expect(() => el.render()).to.not.throw; - const cite = el.shadowRoot.querySelector('cite'); + const cite = el.shadowRoot.querySelector("cite"); expect(cite).to.exist; }); it("handles missing sibling for sibling scope", async () => { const container = await fixture(html`
    - +
    `); - - const citation = container.querySelector('citation-element'); - expect(() => citation._scopeChanged('sibling')).to.not.throw; + + const citation = container.querySelector("citation-element"); + expect(() => citation._scopeChanged("sibling")).to.not.throw; }); it("handles malformed URLs gracefully", async () => { const el = await fixture(html` - `); - - const sourceLink = el.shadowRoot.querySelector('a'); - expect(sourceLink.getAttribute('href')).to.equal('not-a-url'); + + const sourceLink = el.shadowRoot.querySelector("a"); + expect(sourceLink.getAttribute("href")).to.equal("not-a-url"); }); it("handles very long titles", async () => { - const longTitle = 'A'.repeat(500); + const longTitle = "A".repeat(500); const el = await fixture(html` `); - - const titleLink = el.shadowRoot.querySelector('a'); + + const titleLink = el.shadowRoot.querySelector("a"); expect(titleLink.textContent).to.equal(longTitle); }); it("handles special characters in properties", async () => { - const specialTitle = 'Title with <>&\"\' characters'; + const specialTitle = "Title with <>&\"' characters"; const el = await fixture(html` `); - - const titleMeta = el.shadowRoot.querySelector('meta[property="cc:attributionName"]'); - expect(titleMeta.getAttribute('content')).to.equal(specialTitle); + + const titleMeta = el.shadowRoot.querySelector( + 'meta[property="cc:attributionName"]', + ); + expect(titleMeta.getAttribute("content")).to.equal(specialTitle); }); it("handles rapid property changes", async () => { for (let i = 0; i < 10; i++) { element.title = `Title ${i}`; element.creator = `Creator ${i}`; - element.license = i % 2 === 0 ? 'by' : 'by-sa'; + element.license = i % 2 === 0 ? "by" : "by-sa"; } await element.updateComplete; - - expect(element.title).to.equal('Title 9'); - expect(element.creator).to.equal('Creator 9'); - expect(element.license).to.equal('by-sa'); + + expect(element.title).to.equal("Title 9"); + expect(element.creator).to.equal("Creator 9"); + expect(element.license).to.equal("by-sa"); }); }); describe("Performance and Resource Management", () => { it("efficiently handles multiple license changes", async () => { - const licenses = ['by', 'by-sa', 'cc0', 'by']; - + const licenses = ["by", "by-sa", "cc0", "by"]; + for (const license of licenses) { element.license = license; await element.updateComplete; } - - expect(element.licenseName).to.equal('CC BY'); + + expect(element.licenseName).to.equal("CC BY"); expect(document.head.appendChild.callCount).to.be.at.least(1); }); it("properly cleans up DOM links when removed", () => { - const licenseLink = element._generateLicenseLink('https://test.com'); - const aboutLink = element._generateAboutLink('test', 'https://license.com'); - + const licenseLink = element._generateLicenseLink("https://test.com"); + const aboutLink = element._generateAboutLink( + "test", + "https://license.com", + ); + // Simulate creating new links (should remove old ones) - element._generateLicenseLink('https://new-test.com'); - element._generateAboutLink('new-test', 'https://new-license.com'); - + element._generateLicenseLink("https://new-test.com"); + element._generateAboutLink("new-test", "https://new-license.com"); + expect(document.head.removeChild.calledTwice).to.be.true; }); it("handles concurrent property updates", async () => { const promises = [ - (async () => { element.title = 'Concurrent Title'; })(), - (async () => { element.creator = 'Concurrent Creator'; })(), - (async () => { element.license = 'by-sa'; })(), - (async () => { element.source = 'https://concurrent.com'; })() + (async () => { + element.title = "Concurrent Title"; + })(), + (async () => { + element.creator = "Concurrent Creator"; + })(), + (async () => { + element.license = "by-sa"; + })(), + (async () => { + element.source = "https://concurrent.com"; + })(), ]; - + await Promise.all(promises); await element.updateComplete; - - expect(element.title).to.equal('Concurrent Title'); - expect(element.creator).to.equal('Concurrent Creator'); - expect(element.license).to.equal('by-sa'); - expect(element.source).to.equal('https://concurrent.com'); + + expect(element.title).to.equal("Concurrent Title"); + expect(element.creator).to.equal("Concurrent Creator"); + expect(element.license).to.equal("by-sa"); + expect(element.source).to.equal("https://concurrent.com"); }); }); describe("Styling and CSS Custom Properties", () => { it("applies DDD design system classes", () => { - const licenseLinks = element.shadowRoot.querySelectorAll('.license-link'); + const licenseLinks = element.shadowRoot.querySelectorAll(".license-link"); expect(licenseLinks.length).to.be.greaterThan(0); }); it("uses CSS custom properties for theming", () => { const styles = element.constructor.styles[0].cssText; - expect(styles).to.include('--ddd-spacing-2'); - expect(styles).to.include('--ddd-theme-default-link'); - expect(styles).to.include('--ddd-font-weight-bold'); + expect(styles).to.include("--ddd-spacing-2"); + expect(styles).to.include("--ddd-theme-default-link"); + expect(styles).to.include("--ddd-font-weight-bold"); }); it("handles display method styling", async () => { - element.displayMethod = 'footnote'; + element.displayMethod = "footnote"; await element.updateComplete; - - expect(element.hasAttribute('display-method')).to.be.true; - expect(element.getAttribute('display-method')).to.equal('footnote'); + + expect(element.hasAttribute("display-method")).to.be.true; + expect(element.getAttribute("display-method")).to.equal("footnote"); }); }); @@ -758,7 +828,7 @@ describe("citation-element test", () => {

    Article Title

    Some content with references.

    - { >
    `); - - const citation = container.querySelector('citation-element'); + + const citation = container.querySelector("citation-element"); expect(citation).to.exist; await expect(citation).shadowDom.to.be.accessible(); }); @@ -775,29 +845,29 @@ describe("citation-element test", () => { it("maintains semantic integrity with multiple citations", async () => { const container = await fixture(html`
    - -
    `); - - const citations = container.querySelectorAll('citation-element'); + + const citations = container.querySelectorAll("citation-element"); expect(citations.length).to.equal(2); - + for (const citation of citations) { await expect(citation).shadowDom.to.be.accessible(); } }); it("preserves citation data through DOM manipulations", async () => { - const container = document.createElement('div'); + const container = document.createElement("div"); container.innerHTML = ` { license="cc0" > `; - + document.body.appendChild(container); - const citation = container.querySelector('citation-element'); - - expect(citation.title).to.equal('Persistent Citation'); - expect(citation.creator).to.equal('Persistent Author'); - expect(citation.license).to.equal('cc0'); - + const citation = container.querySelector("citation-element"); + + expect(citation.title).to.equal("Persistent Citation"); + expect(citation.creator).to.equal("Persistent Author"); + expect(citation.license).to.equal("cc0"); + document.body.removeChild(container); }); }); diff --git a/elements/clean-one/.gitignore b/elements/clean-one/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/clean-one/.gitignore +++ b/elements/clean-one/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/clean-one/clean-one.js b/elements/clean-one/clean-one.js index 5e567a792b..3dae68bda7 100644 --- a/elements/clean-one/clean-one.js +++ b/elements/clean-one/clean-one.js @@ -28,6 +28,8 @@ import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Course + * @haxcms-theme-internal false * @demo demo/index.html * @element clean-one */ diff --git a/elements/clean-one/custom-elements.json b/elements/clean-one/custom-elements.json index 80516e35f3..ed2c2339bb 100644 --- a/elements/clean-one/custom-elements.json +++ b/elements/clean-one/custom-elements.json @@ -8,7 +8,7 @@ "declarations": [ { "kind": "class", - "description": "`clean-one`\n`Clean HAXcms theme, one.`", + "description": "`Clean one`", "name": "CleanOne", "members": [ { diff --git a/elements/clean-one/demo/index.html b/elements/clean-one/demo/index.html index 7885a4c621..99f03e73b8 100644 --- a/elements/clean-one/demo/index.html +++ b/elements/clean-one/demo/index.html @@ -4,15 +4,14 @@ CleanOne: clean-one Demo - - +
    diff --git a/elements/clean-one/index.html b/elements/clean-one/index.html index 9127966793..773a8e9de9 100644 --- a/elements/clean-one/index.html +++ b/elements/clean-one/index.html @@ -4,10 +4,10 @@ clean-one documentation - - + + - + diff --git a/elements/clean-one/package.json b/elements/clean-one/package.json index 404c9f19e8..527528f0b1 100644 --- a/elements/clean-one/package.json +++ b/elements/clean-one/package.json @@ -52,17 +52,14 @@ "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/simple-popover": "^11.0.0", "@haxtheweb/utils": "^11.0.0", - "lit": "3.3.0", + "lit": "3.3.1", "mobx": "6.13.7" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/clean-one/test/clean-one.test.js b/elements/clean-one/test/clean-one.test.js index 473b57ce79..3b0d094a4c 100644 --- a/elements/clean-one/test/clean-one.test.js +++ b/elements/clean-one/test/clean-one.test.js @@ -41,7 +41,7 @@ describe("clean-one accessibility tests", () => { const el = await fixture(html` `); const searchElement = el.shadowRoot.querySelector('[role="search"]'); const mainElement = el.shadowRoot.querySelector('[role="main"]'); - + expect(searchElement).to.exist; expect(mainElement).to.exist; expect(mainElement.tagName).to.equal("MAIN"); @@ -49,10 +49,10 @@ describe("clean-one accessibility tests", () => { it("maintains accessible navigation structure", async () => { const el = await fixture(html` `); - const nav = el.shadowRoot.querySelector('site-menu-button'); - const header = el.shadowRoot.querySelector('header'); - const footer = el.shadowRoot.querySelector('footer'); - + const nav = el.shadowRoot.querySelector("site-menu-button"); + const header = el.shadowRoot.querySelector("header"); + const footer = el.shadowRoot.querySelector("footer"); + expect(header).to.exist; expect(footer).to.exist; expect(nav).to.exist; @@ -60,9 +60,9 @@ describe("clean-one accessibility tests", () => { it("has proper semantic HTML structure", async () => { const el = await fixture(html` `); - const article = el.shadowRoot.querySelector('article'); - const section = el.shadowRoot.querySelector('section'); - + const article = el.shadowRoot.querySelector("article"); + const section = el.shadowRoot.querySelector("section"); + expect(article).to.exist; expect(section).to.exist; }); @@ -71,47 +71,49 @@ describe("clean-one accessibility tests", () => { // Property validation tests describe("clean-one property validation", () => { it("accepts valid searchTerm string", async () => { - const el = await fixture(html``); - expect(el.searchTerm).to.equal('test search'); - expect(typeof el.searchTerm).to.equal('string'); + const el = await fixture( + html``, + ); + expect(el.searchTerm).to.equal("test search"); + expect(typeof el.searchTerm).to.equal("string"); }); it("handles empty searchTerm", async () => { - const el = await fixture(html``); - expect(el.searchTerm).to.equal(''); + const el = await fixture(html``); + expect(el.searchTerm).to.equal(""); }); it("updates searchTerm property reactively", async () => { const el = await fixture(html``); - el.searchTerm = 'new search'; + el.searchTerm = "new search"; await el.updateComplete; - expect(el.searchTerm).to.equal('new search'); + expect(el.searchTerm).to.equal("new search"); }); it("validates property types correctly", async () => { const el = await fixture(html``); - + // Test that searchTerm accepts strings - el.searchTerm = 'string value'; - expect(typeof el.searchTerm).to.equal('string'); - + el.searchTerm = "string value"; + expect(typeof el.searchTerm).to.equal("string"); + // Test inherited properties exist - expect(el.hasOwnProperty('editMode')).to.be.true; - expect(el.hasOwnProperty('responsiveSize')).to.be.true; + expect(el.hasOwnProperty("editMode")).to.be.true; + expect(el.hasOwnProperty("responsiveSize")).to.be.true; }); }); // Slot usage and content tests describe("clean-one slot usage", () => { it("renders default slot content correctly", async () => { - const testContent = '

    Test content in slot

    '; + const testContent = "

    Test content in slot

    "; const el = await fixture(html`${testContent}`); - - const slot = el.shadowRoot.querySelector('#slot slot'); + + const slot = el.shadowRoot.querySelector("#slot slot"); expect(slot).to.exist; - + // Check that slotted content is accessible - const slottedElements = slot.assignedNodes({flatten: true}); + const slottedElements = slot.assignedNodes({ flatten: true }); expect(slottedElements.length).to.be.greaterThan(0); }); @@ -123,9 +125,11 @@ describe("clean-one slot usage", () => {
    Test div
    `); - - const slot = el.shadowRoot.querySelector('#slot slot'); - const slottedElements = slot.assignedNodes({flatten: true}).filter(node => node.nodeType === Node.ELEMENT_NODE); + + const slot = el.shadowRoot.querySelector("#slot slot"); + const slottedElements = slot + .assignedNodes({ flatten: true }) + .filter((node) => node.nodeType === Node.ELEMENT_NODE); expect(slottedElements.length).to.equal(3); }); @@ -138,7 +142,7 @@ describe("clean-one slot usage", () => { `); - + await expect(el).to.be.accessible(); }); }); @@ -147,56 +151,57 @@ describe("clean-one slot usage", () => { describe("clean-one search functionality", () => { it("handles search input changes", async () => { const el = await fixture(html``); - const searchBox = el.shadowRoot.querySelector('clean-one-search-box'); - + const searchBox = el.shadowRoot.querySelector("clean-one-search-box"); + expect(searchBox).to.exist; - expect(el.searchTerm).to.equal(''); + expect(el.searchTerm).to.equal(""); }); it("shows/hides search results appropriately", async () => { const el = await fixture(html``); - + // Initially search should be hidden - const searchElement = el.shadowRoot.querySelector('site-search'); - expect(searchElement?.hasAttribute('hidden')).to.be.true; - + const searchElement = el.shadowRoot.querySelector("site-search"); + expect(searchElement && searchElement.hasAttribute("hidden")).to.be.true; + // Set search term - el.searchTerm = 'test'; + el.searchTerm = "test"; await el.updateComplete; - + // Search should now be visible - expect(searchElement?.hasAttribute('hidden')).to.be.false; + expect(searchElement && searchElement.hasAttribute("hidden")).to.be.false; }); it("hides main content when searching", async () => { const el = await fixture(html``); - - el.searchTerm = 'test search'; + + el.searchTerm = "test search"; await el.updateComplete; - - const contentContainer = el.shadowRoot.querySelector('#contentcontainer'); - expect(contentContainer?.hasAttribute('hidden')).to.be.true; + + const contentContainer = el.shadowRoot.querySelector("#contentcontainer"); + expect(contentContainer && contentContainer.hasAttribute("hidden")).to.be + .true; }); }); // Mobile responsiveness tests describe("clean-one mobile responsiveness", () => { beforeEach(async () => { - await setViewport({width: 375, height: 750}); + await setViewport({ width: 375, height: 750 }); }); afterEach(async () => { - await setViewport({width: 1024, height: 768}); + await setViewport({ width: 1024, height: 768 }); }); it("adapts to mobile viewport", async () => { const el = await fixture(html``); expect(el).to.exist; - + // Check that the element renders in mobile viewport await el.updateComplete; const computedStyle = getComputedStyle(el); - expect(computedStyle.display).to.not.equal('none'); + expect(computedStyle.display).to.not.equal("none"); }); it("maintains accessibility on mobile", async () => { @@ -208,44 +213,45 @@ describe("clean-one mobile responsiveness", () => { // Desktop responsiveness tests describe("clean-one desktop responsiveness", () => { beforeEach(async () => { - await setViewport({width: 1200, height: 800}); + await setViewport({ width: 1200, height: 800 }); }); afterEach(async () => { - await setViewport({width: 1024, height: 768}); + await setViewport({ width: 1024, height: 768 }); }); it("adapts to desktop viewport", async () => { const el = await fixture(html``); expect(el).to.exist; - + await el.updateComplete; const computedStyle = getComputedStyle(el); - expect(computedStyle.display).to.not.equal('none'); + expect(computedStyle.display).to.not.equal("none"); }); it("shows navigation elements on desktop", async () => { const el = await fixture(html``); - const navigation = el.shadowRoot.querySelector('.navigation'); + const navigation = el.shadowRoot.querySelector(".navigation"); expect(navigation).to.exist; }); }); -// Theme integration and DDD usage tests +// Theme integration and DDD usage tests describe("clean-one DDD integration", () => { it("uses DDD design system tokens", async () => { const el = await fixture(html``); const styles = getComputedStyle(el); - + // Check that DDD CSS custom properties are being used - const cssText = el.constructor.styles[el.constructor.styles.length - 1].cssText; - expect(cssText).to.include('--ddd-'); + const cssText = + el.constructor.styles[el.constructor.styles.length - 1].cssText; + expect(cssText).to.include("--ddd-"); }); it("extends HAXCMSLitElementTheme properly", async () => { const el = await fixture(html``); expect(el.HAXCMSThemeSettings).to.exist; - expect(typeof el.HAXCMSThemeSettings).to.equal('object'); + expect(typeof el.HAXCMSThemeSettings).to.equal("object"); }); }); @@ -253,21 +259,21 @@ describe("clean-one DDD integration", () => { describe("clean-one menu functionality", () => { it("handles menu open/close states", async () => { const el = await fixture(html``); - + // Initially menu should be closed - expect(el.hasAttribute('menu-open')).to.be.false; - + expect(el.hasAttribute("menu-open")).to.be.false; + // Test menu button exists - const menuButton = el.shadowRoot.querySelector('site-menu-button'); + const menuButton = el.shadowRoot.querySelector("site-menu-button"); expect(menuButton).to.exist; }); it("renders menu structure correctly", async () => { const el = await fixture(html``); - - const menuOutline = el.shadowRoot.querySelector('.menu-outline'); - const searchInput = el.shadowRoot.querySelector('#site-search-input'); - + + const menuOutline = el.shadowRoot.querySelector(".menu-outline"); + const searchInput = el.shadowRoot.querySelector("#site-search-input"); + expect(menuOutline).to.exist; expect(searchInput).to.exist; }); @@ -277,7 +283,7 @@ describe("clean-one menu functionality", () => { describe("clean-one error handling", () => { it("handles missing or invalid content gracefully", async () => { const el = await fixture(html``); - + // Element should still render without content expect(el).to.exist; await expect(el).to.be.accessible(); @@ -285,22 +291,22 @@ describe("clean-one error handling", () => { it("maintains functionality with special characters in search", async () => { const el = await fixture(html``); - - el.searchTerm = '!@#$%^&*()[]{}"\''; + + el.searchTerm = "!@#$%^&*()[]{}\"'"; await el.updateComplete; - - expect(el.searchTerm).to.equal('!@#$%^&*()[]{}"\\''); + + expect(el.searchTerm).to.equal("!@#$%^&*()[]{}\"\\'"); }); it("handles rapid property changes", async () => { const el = await fixture(html``); - + // Rapidly change search term - for(let i = 0; i < 10; i++) { + for (let i = 0; i < 10; i++) { el.searchTerm = `search ${i}`; } - + await el.updateComplete; - expect(el.searchTerm).to.equal('search 9'); + expect(el.searchTerm).to.equal("search 9"); }); }); diff --git a/elements/clean-portfolio-theme/clean-portfolio-theme.js b/elements/clean-portfolio-theme/clean-portfolio-theme.js index 9339534b52..dc110f8e97 100644 --- a/elements/clean-portfolio-theme/clean-portfolio-theme.js +++ b/elements/clean-portfolio-theme/clean-portfolio-theme.js @@ -120,10 +120,12 @@ export class CleanPortfolioTheme extends DDDSuper(HAXCMSLitElementTheme) { ); if (parent) { - const category = active.metadata.tags?.split(",").map(tag => tag.trim())?.[0] || null; + const activeTags = active.metadata.tags && active.metadata.tags.split(",").map(tag => tag.trim()); + const category = (activeTags && activeTags[0]) || null; const siblings = store.manifest.items .filter((item) => { - const itemCategory = item.metadata?.tags?.split(",").map(tag => tag.trim())?.[0] || null; + const itemTags = item.metadata && item.metadata.tags && item.metadata.tags.split(",").map(tag => tag.trim()); + const itemCategory = (itemTags && itemTags[0]) || null; return ( item.parent === active.parent && itemCategory === category diff --git a/elements/clean-portfolio-theme/package.json b/elements/clean-portfolio-theme/package.json index eb6351a408..27a82f7f92 100644 --- a/elements/clean-portfolio-theme/package.json +++ b/elements/clean-portfolio-theme/package.json @@ -30,7 +30,7 @@ "dependencies": { "@haxtheweb/d-d-d": "^11.0.5", "@haxtheweb/i18n-manager": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@babel/preset-env": "^7.16.4", diff --git a/elements/clean-two/.gitignore b/elements/clean-two/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/clean-two/.gitignore +++ b/elements/clean-two/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/clean-two/clean-two.js b/elements/clean-two/clean-two.js index a7339d2146..9abc800b06 100644 --- a/elements/clean-two/clean-two.js +++ b/elements/clean-two/clean-two.js @@ -31,6 +31,8 @@ import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Course + * @haxcms-theme-internal false * @demo demo/index.html * @element clean-two */ diff --git a/elements/clean-two/custom-elements.json b/elements/clean-two/custom-elements.json index e67ba279af..7d13458c22 100644 --- a/elements/clean-two/custom-elements.json +++ b/elements/clean-two/custom-elements.json @@ -8,7 +8,7 @@ "declarations": [ { "kind": "class", - "description": "`clean-two`\n`A 2nd clean theme`", + "description": "`Clean two`", "name": "CleanTwo", "members": [ { diff --git a/elements/clean-two/demo/index.html b/elements/clean-two/demo/index.html index 72b37c3b6e..377b6e9fc6 100644 --- a/elements/clean-two/demo/index.html +++ b/elements/clean-two/demo/index.html @@ -4,15 +4,14 @@ CleanTwo: clean-two Demo - - +
    diff --git a/elements/clean-two/index.html b/elements/clean-two/index.html index 5f23264af8..53843eb71a 100644 --- a/elements/clean-two/index.html +++ b/elements/clean-two/index.html @@ -4,10 +4,10 @@ clean-two documentation - - + + - + diff --git a/elements/clean-two/package.json b/elements/clean-two/package.json index 75c0d5e502..e8026bd222 100644 --- a/elements/clean-two/package.json +++ b/elements/clean-two/package.json @@ -49,17 +49,14 @@ "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/utils": "^11.0.0", - "lit": "3.3.0", + "lit": "3.3.1", "mobx": "6.13.7" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/clean-two/test/clean-two.test.js b/elements/clean-two/test/clean-two.test.js index 03e9b62260..44dd0df2e4 100644 --- a/elements/clean-two/test/clean-two.test.js +++ b/elements/clean-two/test/clean-two.test.js @@ -42,12 +42,12 @@ describe("clean-two accessibility tests", () => { it("has proper semantic HTML structure", async () => { const el = await fixture(html` `); - const nav = el.shadowRoot.querySelector('nav'); - const main = el.shadowRoot.querySelector('main'); - const header = el.shadowRoot.querySelector('header'); - const footer = el.shadowRoot.querySelector('footer'); - const article = el.shadowRoot.querySelector('article'); - + const nav = el.shadowRoot.querySelector("nav"); + const main = el.shadowRoot.querySelector("main"); + const header = el.shadowRoot.querySelector("header"); + const footer = el.shadowRoot.querySelector("footer"); + const article = el.shadowRoot.querySelector("article"); + expect(nav).to.exist; expect(main).to.exist; expect(header).to.exist; @@ -57,10 +57,14 @@ describe("clean-two accessibility tests", () => { it("maintains accessible navigation structure", async () => { const el = await fixture(html` `); - const prevButton = el.shadowRoot.querySelector('site-menu-button[type="prev"]'); - const nextButton = el.shadowRoot.querySelector('site-menu-button[type="next"]'); - const breadcrumb = el.shadowRoot.querySelector('site-breadcrumb'); - + const prevButton = el.shadowRoot.querySelector( + 'site-menu-button[type="prev"]', + ); + const nextButton = el.shadowRoot.querySelector( + 'site-menu-button[type="next"]', + ); + const breadcrumb = el.shadowRoot.querySelector("site-breadcrumb"); + expect(prevButton).to.exist; expect(nextButton).to.exist; expect(breadcrumb).to.exist; @@ -68,12 +72,12 @@ describe("clean-two accessibility tests", () => { it("has proper search accessibility", async () => { const el = await fixture(html` `); - const searchModal = el.shadowRoot.querySelector('site-modal'); - const searchComponent = el.shadowRoot.querySelector('site-search'); - + const searchModal = el.shadowRoot.querySelector("site-modal"); + const searchComponent = el.shadowRoot.querySelector("site-search"); + expect(searchModal).to.exist; expect(searchComponent).to.exist; - expect(searchModal.getAttribute('title')).to.equal('Search site'); + expect(searchModal.getAttribute("title")).to.equal("Search site"); }); it("maintains accessibility with slotted content", async () => { @@ -83,7 +87,7 @@ describe("clean-two accessibility tests", () => {

    Test content for accessibility

    `); - + await expect(el).to.be.accessible(); }); }); @@ -91,67 +95,75 @@ describe("clean-two accessibility tests", () => { // Property validation tests describe("clean-two property validation", () => { it("accepts valid searchTerm string", async () => { - const el = await fixture(html``); - expect(el.searchTerm).to.equal('test search'); - expect(typeof el.searchTerm).to.equal('string'); + const el = await fixture( + html``, + ); + expect(el.searchTerm).to.equal("test search"); + expect(typeof el.searchTerm).to.equal("string"); }); it("accepts valid prevPage string", async () => { - const el = await fixture(html``); - expect(el.prevPage).to.equal('Previous Page Title'); - expect(typeof el.prevPage).to.equal('string'); + const el = await fixture( + html``, + ); + expect(el.prevPage).to.equal("Previous Page Title"); + expect(typeof el.prevPage).to.equal("string"); }); it("accepts valid nextPage string", async () => { - const el = await fixture(html``); - expect(el.nextPage).to.equal('Next Page Title'); - expect(typeof el.nextPage).to.equal('string'); + const el = await fixture( + html``, + ); + expect(el.nextPage).to.equal("Next Page Title"); + expect(typeof el.nextPage).to.equal("string"); }); it("accepts valid pageTimestamp number", async () => { const timestamp = Date.now(); - const el = await fixture(html``); + const el = await fixture( + html``, + ); expect(el.pageTimestamp).to.equal(timestamp); - expect(typeof el.pageTimestamp).to.equal('number'); + expect(typeof el.pageTimestamp).to.equal("number"); }); it("updates properties reactively", async () => { const el = await fixture(html``); - - el.searchTerm = 'new search'; - el.prevPage = 'Previous'; - el.nextPage = 'Next'; + + el.searchTerm = "new search"; + el.prevPage = "Previous"; + el.nextPage = "Next"; el.pageTimestamp = 1234567890; - + await el.updateComplete; - - expect(el.searchTerm).to.equal('new search'); - expect(el.prevPage).to.equal('Previous'); - expect(el.nextPage).to.equal('Next'); + + expect(el.searchTerm).to.equal("new search"); + expect(el.prevPage).to.equal("Previous"); + expect(el.nextPage).to.equal("Next"); expect(el.pageTimestamp).to.equal(1234567890); }); it("validates inherited properties from theme mixins", async () => { const el = await fixture(html``); - + // Test that HAXCMSLitElementTheme properties exist - expect(el.hasOwnProperty('editMode')).to.be.true; - expect(el.hasOwnProperty('responsiveSize')).to.be.true; + expect(el.hasOwnProperty("editMode")).to.be.true; + expect(el.hasOwnProperty("responsiveSize")).to.be.true; expect(el.HAXCMSThemeSettings).to.exist; - expect(typeof el.HAXCMSThemeSettings).to.equal('object'); + expect(typeof el.HAXCMSThemeSettings).to.equal("object"); }); }); // Slot usage and content tests describe("clean-two slot usage", () => { it("renders default slot content correctly", async () => { - const testContent = '

    Test content in slot

    '; + const testContent = "

    Test content in slot

    "; const el = await fixture(html`${testContent}`); - - const slot = el.shadowRoot.querySelector('#slot slot'); + + const slot = el.shadowRoot.querySelector("#slot slot"); expect(slot).to.exist; - - const slottedElements = slot.assignedNodes({flatten: true}); + + const slottedElements = slot.assignedNodes({ flatten: true }); expect(slottedElements.length).to.be.greaterThan(0); }); @@ -161,12 +173,16 @@ describe("clean-two slot usage", () => {

    Article Title

    First paragraph

    Second paragraph

    -
    • List item
    +
      +
    • List item
    • +
    `); - - const slot = el.shadowRoot.querySelector('#slot slot'); - const slottedElements = slot.assignedNodes({flatten: true}).filter(node => node.nodeType === Node.ELEMENT_NODE); + + const slot = el.shadowRoot.querySelector("#slot slot"); + const slottedElements = slot + .assignedNodes({ flatten: true }) + .filter((node) => node.nodeType === Node.ELEMENT_NODE); expect(slottedElements.length).to.equal(4); }); @@ -183,7 +199,7 @@ describe("clean-two slot usage", () => { `); - + await expect(el).to.be.accessible(); }); @@ -193,15 +209,15 @@ describe("clean-two slot usage", () => {

    This content should be hidden during search

    `); - - const contentContainer = el.shadowRoot.querySelector('#contentcontainer'); - expect(contentContainer.hasAttribute('hidden')).to.be.false; - + + const contentContainer = el.shadowRoot.querySelector("#contentcontainer"); + expect(contentContainer.hasAttribute("hidden")).to.be.false; + // Set search term - el.searchTerm = 'test search'; + el.searchTerm = "test search"; await el.updateComplete; - - expect(contentContainer.hasAttribute('hidden')).to.be.true; + + expect(contentContainer.hasAttribute("hidden")).to.be.true; }); }); @@ -209,38 +225,40 @@ describe("clean-two slot usage", () => { describe("clean-two search functionality", () => { it("handles search modal interaction", async () => { const el = await fixture(html``); - const searchModal = el.shadowRoot.querySelector('site-modal'); - + const searchModal = el.shadowRoot.querySelector("site-modal"); + expect(searchModal).to.exist; - expect(searchModal.getAttribute('icon')).to.equal('icons:search'); - expect(searchModal.getAttribute('title')).to.equal('Search site'); + expect(searchModal.getAttribute("icon")).to.equal("icons:search"); + expect(searchModal.getAttribute("title")).to.equal("Search site"); }); it("shows/hides search results appropriately", async () => { const el = await fixture(html``); - + // Initially search should be hidden - const searchElement = el.shadowRoot.querySelector('site-search[hide-input]'); - expect(searchElement.hasAttribute('hidden')).to.be.true; - + const searchElement = el.shadowRoot.querySelector( + "site-search[hide-input]", + ); + expect(searchElement.hasAttribute("hidden")).to.be.true; + // Set search term - el.searchTerm = 'test'; + el.searchTerm = "test"; await el.updateComplete; - + // Search should now be visible - expect(searchElement.hasAttribute('hidden')).to.be.false; - expect(searchElement.getAttribute('search')).to.equal('test'); + expect(searchElement.hasAttribute("hidden")).to.be.false; + expect(searchElement.getAttribute("search")).to.equal("test"); }); it("handles search term changes", async () => { const el = await fixture(html``); - - expect(el.searchTerm).to.equal(''); - - el.searchTerm = 'new search term'; + + expect(el.searchTerm).to.equal(""); + + el.searchTerm = "new search term"; await el.updateComplete; - - expect(el.searchTerm).to.equal('new search term'); + + expect(el.searchTerm).to.equal("new search term"); }); }); @@ -248,35 +266,41 @@ describe("clean-two search functionality", () => { describe("clean-two navigation functionality", () => { it("renders navigation buttons correctly", async () => { const el = await fixture(html``); - - const prevButton = el.shadowRoot.querySelector('site-menu-button[type="prev"]'); - const nextButton = el.shadowRoot.querySelector('site-menu-button[type="next"]'); - + + const prevButton = el.shadowRoot.querySelector( + 'site-menu-button[type="prev"]', + ); + const nextButton = el.shadowRoot.querySelector( + 'site-menu-button[type="next"]', + ); + expect(prevButton).to.exist; expect(nextButton).to.exist; - expect(prevButton.getAttribute('position')).to.equal('right'); - expect(nextButton.getAttribute('position')).to.equal('left'); + expect(prevButton.getAttribute("position")).to.equal("right"); + expect(nextButton.getAttribute("position")).to.equal("left"); }); it("handles mobile menu correctly", async () => { const el = await fixture(html``); - - const leftCol = el.shadowRoot.querySelector('.left-col'); + + const leftCol = el.shadowRoot.querySelector(".left-col"); expect(leftCol).to.exist; - + // Check that mobile menu button exists - const mobileMenuButton = el.shadowRoot.querySelector('#haxcmsmobilemenubutton'); + const mobileMenuButton = el.shadowRoot.querySelector( + "#haxcmsmobilemenubutton", + ); expect(mobileMenuButton).to.exist; }); it("displays menu content appropriately for different screen sizes", async () => { const el = await fixture(html``); - + // Test XL responsive size behavior - el.responsiveSize = 'xl'; + el.responsiveSize = "xl"; await el.updateComplete; - - const rightCol = el.shadowRoot.querySelector('.right-col'); + + const rightCol = el.shadowRoot.querySelector(".right-col"); expect(rightCol).to.exist; }); }); @@ -284,26 +308,28 @@ describe("clean-two navigation functionality", () => { // Mobile responsiveness tests describe("clean-two mobile responsiveness", () => { beforeEach(async () => { - await setViewport({width: 375, height: 750}); + await setViewport({ width: 375, height: 750 }); }); afterEach(async () => { - await setViewport({width: 1024, height: 768}); + await setViewport({ width: 1024, height: 768 }); }); it("adapts layout for mobile viewport", async () => { const el = await fixture(html``); expect(el).to.exist; - + await el.updateComplete; const computedStyle = getComputedStyle(el); - expect(computedStyle.display).to.not.equal('none'); + expect(computedStyle.display).to.not.equal("none"); }); it("shows mobile menu content correctly", async () => { const el = await fixture(html``); - const mobileMenuContent = el.shadowRoot.querySelector('site-menu-content[mobile]'); - + const mobileMenuContent = el.shadowRoot.querySelector( + "site-menu-content[mobile]", + ); + // Mobile menu content should exist on smaller screens expect(mobileMenuContent).to.exist; }); @@ -317,27 +343,27 @@ describe("clean-two mobile responsiveness", () => { // Desktop responsiveness tests describe("clean-two desktop responsiveness", () => { beforeEach(async () => { - await setViewport({width: 1900, height: 1000}); + await setViewport({ width: 1900, height: 1000 }); }); afterEach(async () => { - await setViewport({width: 1024, height: 768}); + await setViewport({ width: 1024, height: 768 }); }); it("shows right column on XL screens", async () => { const el = await fixture(html``); - + // Manually set XL responsive size to test XL behavior - el.responsiveSize = 'xl'; + el.responsiveSize = "xl"; await el.updateComplete; - - const rightCol = el.shadowRoot.querySelector('.right-col'); + + const rightCol = el.shadowRoot.querySelector(".right-col"); expect(rightCol).to.exist; }); it("adapts navigation for desktop", async () => { const el = await fixture(html``); - const navigation = el.shadowRoot.querySelector('.link-actions'); + const navigation = el.shadowRoot.querySelector(".link-actions"); expect(navigation).to.exist; }); }); @@ -346,24 +372,25 @@ describe("clean-two desktop responsiveness", () => { describe("clean-two DDD integration", () => { it("uses DDD design system tokens", async () => { const el = await fixture(html``); - + // Check that DDD CSS custom properties are being used - const cssText = el.constructor.styles[el.constructor.styles.length - 1].cssText; - expect(cssText).to.include('--ddd-'); - expect(cssText).to.include('var(--ddd-accent-6)'); - expect(cssText).to.include('var(--ddd-primary-4)'); + const cssText = + el.constructor.styles[el.constructor.styles.length - 1].cssText; + expect(cssText).to.include("--ddd-"); + expect(cssText).to.include("var(--ddd-accent-6)"); + expect(cssText).to.include("var(--ddd-primary-4)"); }); it("properly extends HAXCMSLitElementTheme with all mixins", async () => { const el = await fixture(html``); - + expect(el.HAXCMSThemeSettings).to.exist; - expect(typeof el.HAXCMSThemeSettings).to.equal('object'); - + expect(typeof el.HAXCMSThemeSettings).to.equal("object"); + // Test mixin functionality exists - expect(typeof el.QRCodeButton).to.equal('function'); - expect(typeof el.PrintBranchButton).to.equal('function'); - expect(typeof el.PDFPageButton).to.equal('function'); + expect(typeof el.QRCodeButton).to.equal("function"); + expect(typeof el.PrintBranchButton).to.equal("function"); + expect(typeof el.PDFPageButton).to.equal("function"); }); }); @@ -371,11 +398,11 @@ describe("clean-two DDD integration", () => { describe("clean-two layout and structure", () => { it("renders proper page structure", async () => { const el = await fixture(html``); - - const bodyWrapper = el.shadowRoot.querySelector('.body-wrapper'); - const contentWrapper = el.shadowRoot.querySelector('.content-wrapper'); - const leftCol = el.shadowRoot.querySelector('.left-col'); - + + const bodyWrapper = el.shadowRoot.querySelector(".body-wrapper"); + const contentWrapper = el.shadowRoot.querySelector(".content-wrapper"); + const leftCol = el.shadowRoot.querySelector(".left-col"); + expect(bodyWrapper).to.exist; expect(contentWrapper).to.exist; expect(leftCol).to.exist; @@ -383,11 +410,11 @@ describe("clean-two layout and structure", () => { it("includes all required page elements", async () => { const el = await fixture(html``); - - const breadcrumb = el.shadowRoot.querySelector('site-breadcrumb'); - const activeTitle = el.shadowRoot.querySelector('site-active-title'); - const activeTags = el.shadowRoot.querySelector('site-active-tags'); - + + const breadcrumb = el.shadowRoot.querySelector("site-breadcrumb"); + const activeTitle = el.shadowRoot.querySelector("site-active-title"); + const activeTags = el.shadowRoot.querySelector("site-active-tags"); + expect(breadcrumb).to.exist; expect(activeTitle).to.exist; expect(activeTags).to.exist; @@ -395,12 +422,14 @@ describe("clean-two layout and structure", () => { it("renders footer with timestamp correctly", async () => { const timestamp = Date.now(); - const el = await fixture(html``); - - const dateTime = el.shadowRoot.querySelector('simple-datetime'); + const el = await fixture( + html``, + ); + + const dateTime = el.shadowRoot.querySelector("simple-datetime"); expect(dateTime).to.exist; - expect(dateTime.hasAttribute('unix')).to.be.true; - expect(parseInt(dateTime.getAttribute('timestamp'))).to.equal(timestamp); + expect(dateTime.hasAttribute("unix")).to.be.true; + expect(parseInt(dateTime.getAttribute("timestamp"))).to.equal(timestamp); }); }); @@ -408,43 +437,43 @@ describe("clean-two layout and structure", () => { describe("clean-two error handling", () => { it("handles missing or invalid content gracefully", async () => { const el = await fixture(html``); - + expect(el).to.exist; await expect(el).to.be.accessible(); }); it("maintains functionality with special characters in search", async () => { const el = await fixture(html``); - - const specialChars = '!@#$%^&*()[]{}"\''; + + const specialChars = "!@#$%^&*()[]{}\"'"; el.searchTerm = specialChars; await el.updateComplete; - + expect(el.searchTerm).to.equal(specialChars); }); it("handles rapid property changes", async () => { const el = await fixture(html``); - + // Rapidly change multiple properties - for(let i = 0; i < 5; i++) { + for (let i = 0; i < 5; i++) { el.searchTerm = `search ${i}`; el.prevPage = `prev ${i}`; el.nextPage = `next ${i}`; } - + await el.updateComplete; - expect(el.searchTerm).to.equal('search 4'); - expect(el.prevPage).to.equal('prev 4'); - expect(el.nextPage).to.equal('next 4'); + expect(el.searchTerm).to.equal("search 4"); + expect(el.prevPage).to.equal("prev 4"); + expect(el.nextPage).to.equal("next 4"); }); it("handles undefined pageTimestamp gracefully", async () => { const el = await fixture(html``); - + expect(el.pageTimestamp).to.be.undefined; - - const dateTime = el.shadowRoot.querySelector('simple-datetime'); + + const dateTime = el.shadowRoot.querySelector("simple-datetime"); expect(dateTime).to.exist; }); }); diff --git a/elements/cms-hax/.gitignore b/elements/cms-hax/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/cms-hax/.gitignore +++ b/elements/cms-hax/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/cms-hax/custom-elements.json b/elements/cms-hax/custom-elements.json deleted file mode 100755 index 562fc31428..0000000000 --- a/elements/cms-hax/custom-elements.json +++ /dev/null @@ -1,1732 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "cms-hax.js", - "declarations": [ - { - "kind": "class", - "description": "`cms-hax`", - "name": "CmsHax", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "decodeHTMLEntities", - "parameters": [ - { - "name": "text" - } - ] - }, - { - "kind": "method", - "name": "_activeHaxBodyUpdated", - "parameters": [ - { - "name": "ready" - } - ], - "description": "Ensure we've imported our content on initial setup" - }, - { - "kind": "method", - "name": "_computeRedirectOnSave", - "parameters": [ - { - "name": "redirectLocation" - } - ], - "description": "Calculate if we have anywhere to redirect to." - }, - { - "kind": "method", - "name": "_noticeTagChanges", - "parameters": [ - { - "name": "allowedTags" - }, - { - "name": "hidePanelOps" - }, - { - "name": "offsetMargin" - }, - { - "name": "elementAlign" - } - ], - "description": "Set certain data bound values to the store once it's ready" - }, - { - "kind": "method", - "name": "_storeReady", - "parameters": [ - { - "name": "e" - } - ], - "description": "Set certain data bound values to the store once it's ready" - }, - { - "kind": "method", - "name": "_appstoreLoaded", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "_makeAppStore", - "parameters": [ - { - "name": "val" - } - ] - }, - { - "kind": "method", - "name": "__applyMO" - }, - { - "kind": "method", - "name": "_cancelFired", - "parameters": [ - { - "name": "e" - } - ], - "description": "_cancelFired" - }, - { - "kind": "method", - "name": "_saveFired", - "parameters": [ - { - "name": "e" - } - ], - "description": "_saveFired" - }, - { - "kind": "method", - "name": "_handleUpdateResponse", - "parameters": [ - { - "name": "e" - } - ], - "description": "_handleUpdateResponse" - }, - { - "kind": "field", - "name": "windowControllers", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "windowControllersReady", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "windowControllersLoaded", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "ready", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "ready" - }, - { - "kind": "field", - "name": "__lock", - "type": { - "text": "boolean" - }, - "default": "false" - }, - { - "kind": "field", - "name": "endPoint", - "privacy": "public", - "type": { - "text": "null" - }, - "description": "Location to save content to.", - "default": "null", - "attribute": "end-point" - }, - { - "kind": "field", - "name": "openDefault", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Default the panel to open", - "default": "false", - "attribute": "open-default", - "reflects": true - }, - { - "kind": "field", - "name": "hidePanelOps", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Hide the panel operations (save and cancel),", - "default": "false", - "attribute": "hide-panel-ops" - }, - { - "kind": "field", - "name": "elementAlign", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Direction to elementAlign the hax edit panel", - "default": "\"left\"", - "attribute": "element-align" - }, - { - "kind": "field", - "name": "method", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Method to save content.", - "default": "\"PUT\"", - "attribute": "method" - }, - { - "kind": "field", - "name": "syncBody", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "syncBody", - "default": "false", - "attribute": "sync-body" - }, - { - "kind": "field", - "name": "bodyValue", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Only available if syncBody is true; this allows data binding to the value being worked on in hax-body tag", - "default": "\"\"", - "attribute": "body-value" - }, - { - "kind": "field", - "name": "hideMessage", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Flag to hide the toast.", - "default": "false", - "attribute": "hide-message" - }, - { - "kind": "field", - "name": "__imported", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "__imported" - }, - { - "kind": "field", - "name": "offsetMargin", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "offset-margin", - "reflects": true - }, - { - "kind": "field", - "name": "allowedTags", - "privacy": "public", - "type": { - "text": "array" - }, - "description": "allowed Tags, usually as dictated by the input filtering\nlayer of the backend system that HAX is riding on.\nWhile not fullproof, this at least will enforce front-end\nfiltering to match what actually is going to be allowed\nto be saved in the first place.", - "attribute": "allowed-tags" - }, - { - "kind": "field", - "name": "appStoreConnection", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Connection object for talking to an app store.", - "attribute": "app-store-connection" - }, - { - "kind": "field", - "name": "__appStore", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "__appStore" - }, - { - "kind": "field", - "name": "redirectLocation", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Optional URL to redirect to once we save.", - "attribute": "redirect-location" - }, - { - "kind": "field", - "name": "redirectOnSave", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Option to redirect once we save successfully", - "attribute": "redirect-on-save" - } - ], - "events": [ - { - "name": "hax-body-content-changed", - "type": { - "text": "CustomEvent" - } - }, - { - "name": "cms-hax-saved", - "type": { - "text": "CustomEvent" - } - } - ], - "attributes": [ - { - "name": "ready", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "ready" - }, - { - "name": "open-default", - "type": { - "text": "boolean" - }, - "description": "Default the panel to open", - "default": "false", - "fieldName": "openDefault" - }, - { - "name": "hide-panel-ops", - "type": { - "text": "boolean" - }, - "description": "Hide the panel operations (save and cancel),", - "default": "false", - "fieldName": "hidePanelOps" - }, - { - "name": "offset-margin", - "type": { - "text": "string" - }, - "fieldName": "offsetMargin" - }, - { - "name": "element-align", - "type": { - "text": "string" - }, - "description": "Direction to elementAlign the hax edit panel", - "default": "\"left\"", - "fieldName": "elementAlign" - }, - { - "name": "allowed-tags", - "type": { - "text": "array" - }, - "description": "allowed Tags, usually as dictated by the input filtering\nlayer of the backend system that HAX is riding on.\nWhile not fullproof, this at least will enforce front-end\nfiltering to match what actually is going to be allowed\nto be saved in the first place.", - "fieldName": "allowedTags" - }, - { - "name": "end-point", - "type": { - "text": "null" - }, - "description": "Location to save content to.", - "default": "null", - "fieldName": "endPoint" - }, - { - "name": "method", - "type": { - "text": "string" - }, - "description": "Method to save content.", - "default": "\"PUT\"", - "fieldName": "method" - }, - { - "name": "app-store-connection", - "type": { - "text": "string" - }, - "description": "Connection object for talking to an app store.", - "fieldName": "appStoreConnection" - }, - { - "name": "__appStore", - "type": { - "text": "string" - }, - "fieldName": "__appStore" - }, - { - "name": "sync-body", - "type": { - "text": "boolean" - }, - "description": "syncBody", - "default": "false", - "fieldName": "syncBody" - }, - { - "name": "body-value", - "type": { - "text": "string" - }, - "description": "Only available if syncBody is true; this allows data binding to the value being worked on in hax-body tag", - "default": "\"\"", - "fieldName": "bodyValue" - }, - { - "name": "hide-message", - "type": { - "text": "boolean" - }, - "description": "Flag to hide the toast.", - "default": "false", - "fieldName": "hideMessage" - }, - { - "name": "redirect-location", - "type": { - "text": "string" - }, - "description": "Optional URL to redirect to once we save.", - "fieldName": "redirectLocation" - }, - { - "name": "redirect-on-save", - "type": { - "text": "boolean" - }, - "description": "Option to redirect once we save successfully", - "fieldName": "redirectOnSave" - }, - { - "name": "__imported", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "__imported" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "cms-hax", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "CmsHax", - "module": "cms-hax.js" - } - }, - { - "kind": "js", - "name": "CmsHax", - "declaration": { - "name": "CmsHax", - "module": "cms-hax.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/cms-block.js", - "declarations": [ - { - "kind": "class", - "description": "`cms-block`", - "name": "CMSBlock", - "members": [ - { - "kind": "field", - "name": "template", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "_generateBodyData", - "parameters": [ - { - "name": "blockModule" - }, - { - "name": "blockDelta" - } - ], - "description": "Generate body data." - }, - { - "kind": "method", - "name": "haxHooks" - }, - { - "kind": "method", - "name": "haxactiveElementChanged", - "parameters": [ - { - "name": "element" - }, - { - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "haxeditModeChanged", - "parameters": [ - { - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "_handleblockResponse", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "Handle the response from the block processing endpoint" - }, - { - "kind": "method", - "name": "_blockChanged", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "block end point changed" - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "postProcessgetHaxJSONSchema", - "parameters": [ - { - "name": "schema" - } - ], - "description": "Implements getHaxJSONSchema post processing callback." - }, - { - "kind": "field", - "name": "loading", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "attribute": "loading" - }, - { - "kind": "field", - "name": "blockModule", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Module supplying the block", - "attribute": "blockModule" - }, - { - "kind": "field", - "name": "blockDelta", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "A delta value relative to the module", - "attribute": "blockDelta" - }, - { - "kind": "field", - "name": "blockEndPoint", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "block end point updated, change the way we do processing.", - "attribute": "blockEndPoint" - }, - { - "kind": "field", - "name": "bodyData", - "privacy": "public", - "type": { - "text": "object" - }, - "description": "Body data which is just block with some encapsulation.", - "attribute": "bodyData" - }, - { - "kind": "field", - "name": "blockData", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "block data from the end point.", - "attribute": "blockData" - }, - { - "kind": "field", - "name": "blockPrefix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Prefix for the block to be processed", - "attribute": "blockPrefix" - }, - { - "kind": "field", - "name": "blockSuffix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Suffix for the block to be processed", - "attribute": "blockSuffix" - }, - { - "kind": "field", - "name": "haxEditMode", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "hax-edit-mode" - } - ], - "attributes": [ - { - "name": "loading", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "fieldName": "loading" - }, - { - "name": "blockModule", - "type": { - "text": "string" - }, - "description": "Module supplying the block", - "fieldName": "blockModule" - }, - { - "name": "blockDelta", - "type": { - "text": "string" - }, - "description": "A delta value relative to the module", - "fieldName": "blockDelta" - }, - { - "name": "blockEndPoint", - "type": { - "text": "string" - }, - "description": "block end point updated, change the way we do processing.", - "fieldName": "blockEndPoint" - }, - { - "name": "bodyData", - "type": { - "text": "object" - }, - "description": "Body data which is just block with some encapsulation.", - "fieldName": "bodyData" - }, - { - "name": "blockData", - "type": { - "text": "string" - }, - "description": "block data from the end point.", - "fieldName": "blockData" - }, - { - "name": "blockPrefix", - "type": { - "text": "string" - }, - "description": "Prefix for the block to be processed", - "fieldName": "blockPrefix" - }, - { - "name": "blockSuffix", - "type": { - "text": "string" - }, - "description": "Suffix for the block to be processed", - "fieldName": "blockSuffix" - }, - { - "name": "hax-edit-mode", - "type": { - "text": "boolean" - }, - "fieldName": "haxEditMode" - } - ], - "superclass": { - "name": "PolymerElement", - "package": "@polymer/polymer/polymer-element.js" - }, - "tagName": "cms-block", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "CMSBlock", - "module": "lib/cms-block.js" - } - }, - { - "kind": "js", - "name": "CMSBlock", - "declaration": { - "name": "CMSBlock", - "module": "lib/cms-block.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/cms-entity.js", - "declarations": [ - { - "kind": "class", - "description": "`cms-entity`", - "name": "CMSEntity", - "members": [ - { - "kind": "field", - "name": "template", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "haxHooks" - }, - { - "kind": "method", - "name": "haxactiveElementChanged", - "parameters": [ - { - "name": "element" - }, - { - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "haxeditModeChanged", - "parameters": [ - { - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "_generateBodyData", - "parameters": [ - { - "name": "entityType" - }, - { - "name": "entityId" - }, - { - "name": "entityDisplayMode" - } - ], - "description": "Generate body data." - }, - { - "kind": "method", - "name": "_handleEntityResponse", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "Handle the response from the entity processing endpoint" - }, - { - "kind": "method", - "name": "_entityChanged", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "entity end point changed" - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "postProcessgetHaxJSONSchema", - "parameters": [ - { - "name": "schema" - } - ], - "description": "Implements getHaxJSONSchema post processing callback." - }, - { - "kind": "field", - "name": "loading", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "attribute": "loading" - }, - { - "kind": "field", - "name": "entityType", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Type of entity to load", - "attribute": "entityType" - }, - { - "kind": "field", - "name": "entityId", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "ID of the item to load", - "attribute": "entityId" - }, - { - "kind": "field", - "name": "entityDisplayMode", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Display mode of the entity", - "attribute": "entityDisplayMode" - }, - { - "kind": "field", - "name": "entityEndPoint", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "entity end point updated, change the way we do processing.", - "attribute": "entityEndPoint" - }, - { - "kind": "field", - "name": "bodyData", - "privacy": "public", - "type": { - "text": "object" - }, - "description": "Body data which is just entity with some encapsulation.", - "attribute": "bodyData" - }, - { - "kind": "field", - "name": "entityData", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "entity data from the end point.", - "attribute": "entityData" - }, - { - "kind": "field", - "name": "entityPrefix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Prefix for the entity to be processed", - "attribute": "entityPrefix" - }, - { - "kind": "field", - "name": "entitySuffix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Suffix for the entity to be processed", - "attribute": "entitySuffix" - }, - { - "kind": "field", - "name": "haxEditMode", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "hax-edit-mode" - } - ], - "attributes": [ - { - "name": "loading", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "fieldName": "loading" - }, - { - "name": "entityType", - "type": { - "text": "string" - }, - "description": "Type of entity to load", - "fieldName": "entityType" - }, - { - "name": "entityId", - "type": { - "text": "string" - }, - "description": "ID of the item to load", - "fieldName": "entityId" - }, - { - "name": "entityDisplayMode", - "type": { - "text": "string" - }, - "description": "Display mode of the entity", - "fieldName": "entityDisplayMode" - }, - { - "name": "entityEndPoint", - "type": { - "text": "string" - }, - "description": "entity end point updated, change the way we do processing.", - "fieldName": "entityEndPoint" - }, - { - "name": "bodyData", - "type": { - "text": "object" - }, - "description": "Body data which is just entity with some encapsulation.", - "fieldName": "bodyData" - }, - { - "name": "entityData", - "type": { - "text": "string" - }, - "description": "entity data from the end point.", - "fieldName": "entityData" - }, - { - "name": "entityPrefix", - "type": { - "text": "string" - }, - "description": "Prefix for the entity to be processed", - "fieldName": "entityPrefix" - }, - { - "name": "entitySuffix", - "type": { - "text": "string" - }, - "description": "Suffix for the entity to be processed", - "fieldName": "entitySuffix" - }, - { - "name": "hax-edit-mode", - "type": { - "text": "boolean" - }, - "fieldName": "haxEditMode" - } - ], - "superclass": { - "name": "PolymerElement", - "package": "@polymer/polymer/polymer-element.js" - }, - "tagName": "cms-entity", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "CMSEntity", - "module": "lib/cms-entity.js" - } - }, - { - "kind": "js", - "name": "CMSEntity", - "declaration": { - "name": "CMSEntity", - "module": "lib/cms-entity.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/cms-token.js", - "declarations": [ - { - "kind": "class", - "description": "`cms-token`\nRender and process a shortcode / token from a content management system.", - "name": "CMSToken", - "members": [ - { - "kind": "field", - "name": "template", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "_displayModeChanged", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "Display mode value updated." - }, - { - "kind": "method", - "name": "_generateBodyData", - "parameters": [ - { - "name": "token" - }, - { - "name": "$editingState" - } - ], - "description": "Generate body data." - }, - { - "kind": "method", - "name": "_handleTokenResponse", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "Handle the response from the token processing endpoint" - }, - { - "kind": "method", - "name": "_tokenChanged", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "Token end point changed" - }, - { - "kind": "method", - "name": "_windowVisibilityChanged", - "parameters": [ - { - "name": "e" - } - ], - "description": "Window visibility callback to monitor when we are being seen" - }, - { - "kind": "method", - "name": "__tokenClicked", - "parameters": [ - { - "name": "e" - } - ], - "description": "Notice a click on our edit button and set a flag." - }, - { - "kind": "method", - "name": "haxHooks" - }, - { - "kind": "method", - "name": "haxactiveElementChanged", - "parameters": [ - { - "name": "element" - }, - { - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "haxeditModeChanged", - "parameters": [ - { - "name": "value" - } - ] - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "postProcessgetHaxJSONSchema", - "parameters": [ - { - "name": "schema" - } - ], - "description": "Implements getHaxJSONSchema post processing callback." - }, - { - "kind": "field", - "name": "windowControllers", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "loading", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "attribute": "loading" - }, - { - "kind": "field", - "name": "token", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Token changed (somehow) do the token processing.", - "attribute": "token" - }, - { - "kind": "field", - "name": "tokenEndPoint", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Token end point updated, change the way we do processing.", - "attribute": "tokenEndPoint" - }, - { - "kind": "field", - "name": "bodyData", - "privacy": "public", - "type": { - "text": "object" - }, - "description": "Body data which is just token with some encapsulation.", - "attribute": "bodyData" - }, - { - "kind": "field", - "name": "_clickInvoked", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "internal tracking for edit button being clicked in HAX presentation", - "attribute": "_clickInvoked" - }, - { - "kind": "field", - "name": "tokenData", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Token data from the end point.", - "attribute": "tokenData" - }, - { - "kind": "field", - "name": "tokenPrefix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Prefix for the token to be processed", - "attribute": "tokenPrefix" - }, - { - "kind": "field", - "name": "tokenSuffix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Suffix for the token to be processed", - "attribute": "tokenSuffix" - }, - { - "kind": "field", - "name": "_displayMode", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "_displayMode" - }, - { - "kind": "field", - "name": "haxEditMode", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "hax-edit-mode" - } - ], - "attributes": [ - { - "name": "loading", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "fieldName": "loading" - }, - { - "name": "token", - "type": { - "text": "string" - }, - "description": "Token changed (somehow) do the token processing.", - "fieldName": "token" - }, - { - "name": "tokenEndPoint", - "type": { - "text": "string" - }, - "description": "Token end point updated, change the way we do processing.", - "fieldName": "tokenEndPoint" - }, - { - "name": "bodyData", - "type": { - "text": "object" - }, - "description": "Body data which is just token with some encapsulation.", - "fieldName": "bodyData" - }, - { - "name": "_clickInvoked", - "type": { - "text": "string" - }, - "description": "internal tracking for edit button being clicked in HAX presentation", - "fieldName": "_clickInvoked" - }, - { - "name": "tokenData", - "type": { - "text": "string" - }, - "description": "Token data from the end point.", - "fieldName": "tokenData" - }, - { - "name": "tokenPrefix", - "type": { - "text": "string" - }, - "description": "Prefix for the token to be processed", - "fieldName": "tokenPrefix" - }, - { - "name": "tokenSuffix", - "type": { - "text": "string" - }, - "description": "Suffix for the token to be processed", - "fieldName": "tokenSuffix" - }, - { - "name": "_displayMode", - "type": { - "text": "string" - }, - "fieldName": "_displayMode" - }, - { - "name": "hax-edit-mode", - "type": { - "text": "boolean" - }, - "fieldName": "haxEditMode" - } - ], - "superclass": { - "name": "PolymerElement", - "package": "@polymer/polymer/polymer-element.js" - } - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "CMSToken", - "module": "lib/cms-token.js" - } - }, - { - "kind": "js", - "name": "CMSToken", - "declaration": { - "name": "CMSToken", - "module": "lib/cms-token.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/cms-views.js", - "declarations": [ - { - "kind": "class", - "description": "`cms-views`", - "name": "CMSViews", - "members": [ - { - "kind": "field", - "name": "template", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "haxHooks" - }, - { - "kind": "method", - "name": "haxactiveElementChanged", - "parameters": [ - { - "name": "element" - }, - { - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "haxeditModeChanged", - "parameters": [ - { - "name": "value" - } - ] - }, - { - "kind": "method", - "name": "_generateBodyData", - "parameters": [ - { - "name": "name" - }, - { - "name": "display" - } - ], - "description": "Generate body data." - }, - { - "kind": "method", - "name": "_handleviewsResponse", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "Handle the response from the views processing endpoint" - }, - { - "kind": "method", - "name": "_viewsChanged", - "parameters": [ - { - "name": "newValue" - }, - { - "name": "oldValue" - } - ], - "description": "views end point changed" - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "postProcessgetHaxJSONSchema", - "parameters": [ - { - "name": "schema" - } - ], - "description": "Implements getHaxJSONSchema post processing callback." - }, - { - "kind": "field", - "name": "loading", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "attribute": "loading" - }, - { - "kind": "field", - "name": "viewsName", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Name of the views to render", - "attribute": "viewsName" - }, - { - "kind": "field", - "name": "viewsDisplay", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Display from the views", - "attribute": "viewsDisplay" - }, - { - "kind": "field", - "name": "viewsEndPoint", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "views end point updated, change the way we do processing.", - "attribute": "viewsEndPoint" - }, - { - "kind": "field", - "name": "bodyData", - "privacy": "public", - "type": { - "text": "object" - }, - "description": "Body data which is just views with some encapsulation.", - "attribute": "bodyData" - }, - { - "kind": "field", - "name": "viewsData", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "views data from the end point.", - "attribute": "viewsData" - }, - { - "kind": "field", - "name": "viewsPrefix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Prefix for the views to be processed", - "attribute": "viewsPrefix" - }, - { - "kind": "field", - "name": "viewsSuffix", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Suffix for the views to be processed", - "attribute": "viewsSuffix" - }, - { - "kind": "field", - "name": "haxEditMode", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "hax-edit-mode" - } - ], - "attributes": [ - { - "name": "loading", - "type": { - "text": "boolean" - }, - "description": "Loading state", - "fieldName": "loading" - }, - { - "name": "viewsName", - "type": { - "text": "string" - }, - "description": "Name of the views to render", - "fieldName": "viewsName" - }, - { - "name": "viewsDisplay", - "type": { - "text": "string" - }, - "description": "Display from the views", - "fieldName": "viewsDisplay" - }, - { - "name": "viewsEndPoint", - "type": { - "text": "string" - }, - "description": "views end point updated, change the way we do processing.", - "fieldName": "viewsEndPoint" - }, - { - "name": "bodyData", - "type": { - "text": "object" - }, - "description": "Body data which is just views with some encapsulation.", - "fieldName": "bodyData" - }, - { - "name": "viewsData", - "type": { - "text": "string" - }, - "description": "views data from the end point.", - "fieldName": "viewsData" - }, - { - "name": "viewsPrefix", - "type": { - "text": "string" - }, - "description": "Prefix for the views to be processed", - "fieldName": "viewsPrefix" - }, - { - "name": "viewsSuffix", - "type": { - "text": "string" - }, - "description": "Suffix for the views to be processed", - "fieldName": "viewsSuffix" - }, - { - "name": "hax-edit-mode", - "type": { - "text": "boolean" - }, - "fieldName": "haxEditMode" - } - ], - "superclass": { - "name": "PolymerElement", - "package": "@polymer/polymer/polymer-element.js" - }, - "tagName": "cms-views", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "CMSViews", - "module": "lib/cms-views.js" - } - }, - { - "kind": "js", - "name": "CMSViews", - "declaration": { - "name": "CMSViews", - "module": "lib/cms-views.js" - } - } - ] - } - ] -} diff --git a/elements/cms-hax/lib/cms-block.js b/elements/cms-hax/lib/cms-block.js index cf648fa457..4a16454d78 100644 --- a/elements/cms-hax/lib/cms-block.js +++ b/elements/cms-hax/lib/cms-block.js @@ -2,7 +2,6 @@ import { html, PolymerElement } from "@polymer/polymer/polymer-element.js"; import { FlattenedNodesObserver } from "@polymer/polymer/lib/utils/flattened-nodes-observer.js"; import { microTask } from "@polymer/polymer/lib/utils/async.js"; import "@polymer/iron-ajax/iron-ajax.js"; -import "@polymer/paper-spinner/paper-spinner.js"; import { wipeSlot } from "@haxtheweb/utils/utils.js"; /** * `cms-block` @@ -23,13 +22,7 @@ class CMSBlock extends PolymerElement { :host([hax-edit-mode]) #replacementcontent { pointer-events: none; } - paper-spinner { - visibility: hidden; - opacity: 0; - height: 80px; - width: 80px; - padding: 16px; - } + #replacementcontent { visibility: visible; opacity: 1; @@ -37,10 +30,7 @@ class CMSBlock extends PolymerElement { :host([loading]) { text-align: center; } - :host([loading]) paper-spinner { - visibility: visible; - opacity: 1; - } + :host([loading]) #replacementcontent { opacity: 0; visibility: hidden; @@ -54,7 +44,6 @@ class CMSBlock extends PolymerElement { handle-as="json" last-response="{{blockData}}" > - `; } diff --git a/elements/cms-hax/lib/cms-entity.js b/elements/cms-hax/lib/cms-entity.js index 848b19b0d4..0aabfec58c 100644 --- a/elements/cms-hax/lib/cms-entity.js +++ b/elements/cms-hax/lib/cms-entity.js @@ -2,7 +2,6 @@ import { html, PolymerElement } from "@polymer/polymer/polymer-element.js"; import { FlattenedNodesObserver } from "@polymer/polymer/lib/utils/flattened-nodes-observer.js"; import { microTask } from "@polymer/polymer/lib/utils/async.js"; import "@polymer/iron-ajax/iron-ajax.js"; -import "@polymer/paper-spinner/paper-spinner.js"; import { wipeSlot } from "@haxtheweb/utils/utils.js"; /** * `cms-entity` @@ -23,13 +22,7 @@ class CMSEntity extends PolymerElement { :host([hax-edit-mode]) #replacementcontent { pointer-events: none; } - paper-spinner { - visibility: hidden; - opacity: 0; - height: 80px; - width: 80px; - padding: 16px; - } + #replacementcontent { visibility: visible; opacity: 1; @@ -37,10 +30,7 @@ class CMSEntity extends PolymerElement { :host([loading]) { text-align: center; } - :host([loading]) paper-spinner { - visibility: visible; - opacity: 1; - } + :host([loading]) #replacementcontent { opacity: 0; visibility: hidden; @@ -54,7 +44,6 @@ class CMSEntity extends PolymerElement { handle-as="json" last-response="{{entityData}}" > - `; } diff --git a/elements/cms-hax/lib/cms-token.js b/elements/cms-hax/lib/cms-token.js index ca2e8ee891..cc6fec1b63 100644 --- a/elements/cms-hax/lib/cms-token.js +++ b/elements/cms-hax/lib/cms-token.js @@ -2,7 +2,6 @@ import { html, PolymerElement } from "@polymer/polymer/polymer-element.js"; import { FlattenedNodesObserver } from "@polymer/polymer/lib/utils/flattened-nodes-observer.js"; import { microTask } from "@polymer/polymer/lib/utils/async.js"; import "@polymer/iron-ajax/iron-ajax.js"; -import "@polymer/paper-spinner/paper-spinner.js"; import { wipeSlot } from "@haxtheweb/utils/utils.js"; /** `cms-token` @@ -33,16 +32,6 @@ class CMSToken extends PolymerElement { pointer-events: none; } - paper-spinner { - transition: 0.6s all ease; - position: absolute; - visibility: hidden; - display: none; - opacity: 0; - height: 0; - width: 0; - } - #replacementcontent { transition: 0.6s all ease; visibility: visible; @@ -55,16 +44,6 @@ class CMSToken extends PolymerElement { text-align: center; } - :host([loading]) paper-spinner { - visibility: visible; - opacity: 1; - position: relative; - height: 80px; - width: 80px; - padding: 16px; - display: flex; - } - :host([loading]) #replacementcontent { opacity: 0; visibility: hidden; @@ -80,7 +59,6 @@ class CMSToken extends PolymerElement { handle-as="json" last-response="{{tokenData}}" > - diff --git a/elements/cms-hax/lib/cms-views.js b/elements/cms-hax/lib/cms-views.js index 51d83bd99d..b50bad30f6 100644 --- a/elements/cms-hax/lib/cms-views.js +++ b/elements/cms-hax/lib/cms-views.js @@ -3,7 +3,6 @@ import { FlattenedNodesObserver } from "@polymer/polymer/lib/utils/flattened-nod import { microTask } from "@polymer/polymer/lib/utils/async.js"; import { wipeSlot } from "@haxtheweb/utils/utils.js"; import "@polymer/iron-ajax/iron-ajax.js"; -import "@polymer/paper-spinner/paper-spinner.js"; /** * `cms-views` * @element cms-views @@ -23,13 +22,7 @@ class CMSViews extends PolymerElement { :host([hax-edit-mode]) #replacementcontent { pointer-events: none; } - paper-spinner { - visibility: hidden; - opacity: 0; - height: 80px; - width: 80px; - padding: 16px; - } + #replacementcontent { visibility: visible; opacity: 1; @@ -37,10 +30,7 @@ class CMSViews extends PolymerElement { :host([loading]) { text-align: center; } - :host([loading]) paper-spinner { - visibility: visible; - opacity: 1; - } + :host([loading]) #replacementcontent { opacity: 0; visibility: hidden; @@ -54,7 +44,6 @@ class CMSViews extends PolymerElement { handle-as="json" last-response="{{viewsData}}" > - `; } diff --git a/elements/cms-hax/package.json b/elements/cms-hax/package.json old mode 100755 new mode 100644 index 633f20da49..9410092aa4 --- a/elements/cms-hax/package.json +++ b/elements/cms-hax/package.json @@ -40,18 +40,14 @@ "@haxtheweb/h-a-x": "^11.0.5", "@haxtheweb/hax-body": "^11.0.5", "@polymer/iron-ajax": "3.0.1", - "@polymer/paper-spinner": "3.0.2", "@polymer/polymer": "3.5.2", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/code-editor/.gitignore b/elements/code-editor/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/code-editor/.gitignore +++ b/elements/code-editor/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/code-editor/custom-elements.json b/elements/code-editor/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/code-editor/demo/index.html b/elements/code-editor/demo/index.html index 2dea717d6f..7d62becf7a 100644 --- a/elements/code-editor/demo/index.html +++ b/elements/code-editor/demo/index.html @@ -10,7 +10,7 @@ - +
    @@ -69,7 +69,7 @@

    Basic code-editor demo

    - update + `); - - expect(el.placeholder).to.not.include(' - `; - - const codeElement = this.shadowRoot.querySelector("code-sample"); - if (codeElement) { - codeElement.innerHTML = shareCode; - codeElement._updateContent(); - } - } - - // generate print mode link - generatePrintModeLink() { - const currentUrl = new URL(globalThis.location.href); - const baseUrl = `${currentUrl.protocol}//${currentUrl.host}${currentUrl.pathname.split("/").slice(0, -1).join("/")}`; - - // Extract page slug from current URL path - const pathParts = currentUrl.pathname.split("/"); - const currentPageSlug = - pathParts[pathParts.length - 1] || pathParts[pathParts.length - 2]; - - // If we have a valid page slug, add it to the print URL - if ( - currentPageSlug && - currentPageSlug !== "" && - !currentPageSlug.startsWith("x/") - ) { - return `${baseUrl}/x/print?page=${encodeURIComponent(currentPageSlug)}`; - } else { - // Fallback to print without specific page (will use site's default) - return `${baseUrl}/x/print`; - } - } - firstUpdated(changedProperties) { - super.firstUpdated(changedProperties); - this.calculateShareCode(); - } - static get properties() { - return { - ...super.properties, - link: { - type: String, - }, - printModeLink: { - type: String, - }, - }; - } - constructor() { - super(); - this.link = globalThis.location.href; - this.printModeLink = this.generatePrintModeLink(); - } -} -globalThis.customElements.define(HAXCMSShareDialog.tag, HAXCMSShareDialog); -export { HAXCMSShareDialog }; diff --git a/elements/haxcms-elements/lib/core/haxcms-site-builder.js b/elements/haxcms-elements/lib/core/haxcms-site-builder.js index a050d8980f..6c4b165074 100644 --- a/elements/haxcms-elements/lib/core/haxcms-site-builder.js +++ b/elements/haxcms-elements/lib/core/haxcms-site-builder.js @@ -170,130 +170,6 @@ class HAXCMSSiteBuilder extends I18NMixin(LitElement) { } } - /** - * Apply style guide schema merging to HAX elements - * Loads style guide content and merges properties into content elements - */ - async applyStyleGuide(haxElements, preloadedContent = null) { - try { - // 1. Use preloaded content if provided, otherwise load from store - const styleGuideContent = - preloadedContent || (await store.loadStyleGuideContent()); - if (!styleGuideContent) { - return haxElements; - } - - // 2. Convert style guide content to HAXSchema elements - const styleGuideElements = - await this.htmlToHaxElements(styleGuideContent); - - // 3. Create a mapping of tag names to design attributes from page-template elements - const styleGuideMap = new Map(); - - // Define which attributes are design-based and should be applied - // This includes all DDD data attributes and SimpleColors accent-color - const designAttributes = new Set([ - // DDD primary design attributes - "data-primary", - "data-accent", - // DDD font attributes - "data-font-family", - "data-font-weight", - "data-font-size", - // DDD spacing attributes - "data-padding", - "data-margin", - "data-text-align", - "data-float-position", - // DDD design treatment attributes - "data-design-treatment", - "data-instructional-action", - // DDD border attributes - "data-border", - "data-border-radius", - // DDD shadow attributes - "data-box-shadow", - // DDD width attributes - "data-width", - // SimpleColors accent-color (historical) - "accent-color", - ]); - - for (const styleElement of styleGuideElements) { - // Look for page-template elements - if (styleElement && styleElement.tag === "page-template") { - // Check if this template should be used as default - const enforceStyles = - styleElement.properties && - styleElement.properties["enforce-styles"]; - if (enforceStyles && styleElement.content) { - // Get the actual content element inside the page-template - const templateContentElement = await this.htmlToHaxElements( - styleElement.content, - ); - if ( - templateContentElement && - templateContentElement.length > 0 && - templateContentElement[0].tag - ) { - const tagName = templateContentElement[0].tag; - // Extract only design-related properties - const designProperties = {}; - if (templateContentElement[0].properties) { - for (const [key, value] of Object.entries( - templateContentElement[0].properties, - )) { - if (designAttributes.has(key)) { - designProperties[key] = value; - } - } - } - - // Only store if we have design properties to apply - if (Object.keys(designProperties).length > 0) { - styleGuideMap.set(tagName, { - properties: designProperties, - }); - } - } - } - } - } - // 4. Apply style guide properties to matching content elements - const processedElements = haxElements.map((element) => { - if (element && element.tag && styleGuideMap.has(element.tag)) { - const styleGuide = styleGuideMap.get(element.tag); - - // Only apply design attributes that are not already set on the element - // This preserves existing content properties and functional attributes - const currentProperties = element.properties || {}; - const enhancedProperties = { ...currentProperties }; - - // Add design attributes from style guide only if not already present - for (const [key, value] of Object.entries(styleGuide.properties)) { - if (!(key in currentProperties)) { - enhancedProperties[key] = value; - } - } - - return { - ...element, - properties: enhancedProperties, - }; - } - - return element; - }); - - return processedElements; - } catch (error) { - console.warn( - "Style guide processing failed, returning original elements:", - error, - ); - return haxElements; - } - } /** * Simple "two way" data binding from the element below via events */ @@ -945,6 +821,14 @@ class HAXCMSSiteBuilder extends I18NMixin(LitElement) { async _activeItemContentChanged(newValue, activeItem) { var htmlcontent = newValue; if (htmlcontent !== null && activeItem && activeItem.metadata) { + // Check if page-break should be hidden by platform configuration + const platformConfig = + this.manifest && + this.manifest.metadata && + this.manifest.metadata.platform; + const pageBreakHidden = + platformConfig && platformConfig.pageBreak === false; + // force a page break w/ the relevant details in code // this allows the UI to be modified // required fields followed by optional fields if defined @@ -964,31 +848,28 @@ class HAXCMSSiteBuilder extends I18NMixin(LitElement) { ${activeItem.metadata.icon ? `icon="${activeItem.metadata.icon}"` : ``} ${activeItem.metadata.accentColor ? `accent-color="${activeItem.metadata.accentColor}"` : ``} ${activeItem.metadata.theme && activeItem.metadata.theme.key ? `developer-theme="${activeItem.metadata.theme.key}"` : ``} - ${activeItem.metadata.locked ? 'locked="locked"' : ""} + ${activeItem.metadata.linkUrl ? `link-url="${activeItem.metadata.linkUrl}"` : ``} + ${activeItem.metadata.linkTarget ? `link-target="${activeItem.metadata.linkTarget}"` : ``} + ${activeItem.metadata.locked ? 'locked="locked"' : ``} + ${pageBreakHidden ? 'platform-hidden="platform-hidden"' : ``} ${activeItem.metadata.published === false ? "" : 'published="published"'} >${htmlcontent}`; - // Convert HTML to HAXSchema for processing - try { - // Convert the content to HAXSchema elements - const contentHaxElements = await this.htmlToHaxElements(htmlcontent); - // Apply style guide merging - placeholder for future implementation - const processedHaxElements = - await this.applyStyleGuide(contentHaxElements); - // Convert back to HTML - let processedHtml = ""; - for (let element of processedHaxElements) { - const elementNode = haxElementToNode(element); - processedHtml += elementNode.outerHTML; - } - htmlcontent = processedHtml; - } catch (error) { - console.warn( - "HAXSchema processing failed, using original content:", - error, - ); - // Continue with original htmlcontent if processing fails + // If this page has a link URL configured and the user is not logged in, + // append simple redirect messaging for a better user experience + if (activeItem.metadata.linkUrl) { + const linkTarget = activeItem.metadata.linkTarget || "_self"; + const redirectMessage = ` +

    ${activeItem.metadata.linkUrl}

    +

    If the redirect doesn't work, please click the link above.

    + `; + + // Append the redirect message to the content + htmlcontent = htmlcontent + redirectMessage; } + // Previously, style-guide-driven defaults were applied here. + // That behavior has been removed so page content renders as-is. + htmlcontent = encapScript(htmlcontent); wipeSlot(store.themeElement, "*"); store.activeItemContent = htmlcontent; diff --git a/elements/haxcms-elements/lib/core/haxcms-site-dashboard.js b/elements/haxcms-elements/lib/core/haxcms-site-dashboard.js index 0904f45d7b..ef0346adc8 100644 --- a/elements/haxcms-elements/lib/core/haxcms-site-dashboard.js +++ b/elements/haxcms-elements/lib/core/haxcms-site-dashboard.js @@ -64,61 +64,39 @@ class HAXCMSSiteDashboard extends SimpleColors { display: flex; } button.hax-modal-btn { - font-size: 30px; - padding: 8px; - margin: 4px; + font-size: var(--ddd-font-size-s); + padding: var(--ddd-spacing-3) var(--ddd-spacing-5); + margin: var(--ddd-spacing-2); color: white; - background-color: green; - border: 4px solid black; - border-radius: 8px; - font-family: sans-serif; + background-color: var(--ddd-theme-default-skyBlue); + border: 2px solid var(--ddd-theme-default-navy); + border-radius: var(--ddd-radius-sm); + font-family: var(--ddd-font-navigation); + cursor: pointer; + transition: background-color 0.3s ease; } button.hax-modal-btn.cancel { - background-color: red; + background-color: var(--ddd-theme-default-original87Pink); } button.hax-modal-btn:hover, button.hax-modal-btn:focus { - outline: 2px solid black; - cursor: pointer; - background-color: darkgreen; + outline: 2px solid var(--ddd-theme-default-keystoneYellow); + background-color: var(--ddd-theme-default-nittanyNavy); } button.hax-modal-btn.cancel:hover, button.hax-modal-btn.cancel:focus { - background-color: darkred; - } - .title { - color: black; - font-size: 24px; - margin: 0 0 0 16px; - padding: 0; - display: inline-flex; + background-color: var(--ddd-theme-default-error); } @media screen and (max-width: 600px) { - .title { - font-size: 14px; - margin: 0; - } - .toptext { - font-size: 11px; - } button.hax-modal-btn { - font-size: 14px; + font-size: var(--ddd-font-size-xs); + padding: var(--ddd-spacing-2) var(--ddd-spacing-3); } } button { background-color: white; color: black; } - .title-wrapper { - padding: 0 16px; - } - .toptext { - padding: 0; - margin: 0; - font-size: 12px; - font-style: italic; - display: inline-flex; - } .fields-wrapper { height: auto; background-color: white; @@ -140,9 +118,6 @@ class HAXCMSSiteDashboard extends SimpleColors { // render function render() { return html` -
    -

    ${this.siteTitle} settings

    -
    { + // After upload completes, set as page media + const item = toJS(store.activeItem); + if (item && item.id && fileData && fileData.file) { + globalThis.dispatchEvent( + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: item.id, + operation: "setMedia", + media: fileData.file, + }, + }), + ); + store.toast("Page media updated successfully!", 3000, { + hat: "construction", + walking: true, + }); + } + }, + }, + }), + ); + break; case "upload": case "link": case "insert-file": @@ -1145,16 +1214,13 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( modal: true, styles: { "--simple-modal-titlebar-background": "transparent", - "--simple-modal-titlebar-color": "black", + "--simple-modal-titlebar-color": "light-dark(black, white)", "--simple-modal-width": "90vw", "--simple-modal-min-width": "300px", "--simple-modal-z-index": "100000000", "--simple-modal-height": "90vh", "--simple-modal-min-height": "400px", - "--simple-modal-titlebar-height": "64px", - "--simple-modal-titlebar-color": "black", "--simple-modal-titlebar-height": "80px", - "--simple-modal-titlebar-background": "orange", }, }, }), @@ -1365,6 +1431,20 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( // Create page with title - method called by Merlin programs async createPageWithTitle(title, type, templateContent = null) { + // When invoked from the create-page Merlin program, always prefer the + // live Merlin input value so rapid typing + Enter uses the latest text. + const SuperDaemonInstance = + globalThis.SuperDaemonManager && + globalThis.SuperDaemonManager.requestAvailability(); + if ( + SuperDaemonInstance && + SuperDaemonInstance.programName === "create-page" && + SuperDaemonInstance.value && + SuperDaemonInstance.value.trim() !== "" + ) { + title = SuperDaemonInstance.value.trim(); + } + let order = null; let parent = null; const item = toJS(store.activeItem); @@ -1444,6 +1524,65 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( SuperDaemonInstance.close(); } + // Move page under a new parent - method called by Move Page Merlin program + async movePageUnderParent(pageId, newParentId) { + if (!pageId) { + store.toast("Error: No page to move", 3000, { fire: true }); + return; + } + + // Get the page being moved + const pageToMove = await store.findItem(pageId); + if (!pageToMove) { + store.toast("Error: Page not found", 3000, { fire: true }); + return; + } + + // Determine the new order (make it the last child of the new parent) + let newOrder = 0; + if (newParentId) { + const lastChild = toJS(await store.getLastChildItem(newParentId)); + if (lastChild && (lastChild.order || lastChild.order === 0)) { + newOrder = parseInt(lastChild.order) + 1; + } + } else { + // Moving to root - find last root item + const lastChild = toJS(await store.getLastChildItem(null)); + if (lastChild && (lastChild.order || lastChild.order === 0)) { + newOrder = parseInt(lastChild.order) + 1; + } + } + + // Dispatch the save node details event with setParent operation + // Match the structure used by other operations (moveUp, moveDown, indent, outdent) + store.playSound("click"); + globalThis.dispatchEvent( + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: pageId, + operation: "setParent", + parent: newParentId, + order: newOrder, + }, + }), + ); + + // Provide user feedback + const parentTitle = newParentId + ? (await store.findItem(newParentId)).title + : "Root Level"; + store.toast(`Page moved under "${parentTitle}"`, 3000, { + hat: "construction", + walking: true, + }); + + // Close the daemon + SuperDaemonInstance.close(); + } + constructor() { super(); this.__winEvents = { @@ -1452,7 +1591,10 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( "hax-drop-focus-event": "_expandSettingsPanel", "jwt-logged-in": "_jwtLoggedIn", "super-daemon-close": "sdCloseEvent", + "super-daemon-konami-code": "_konamiCodeActivated", }; + enableServices(["core", "haxcms"]); + this.konamiCodeActivated = false; // Track if cheat codes are unlocked this.rpgHat = "none"; this.darkMode = false; this.__editText = "Edit"; @@ -1460,6 +1602,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( this.soundIcon = ""; this.__disposer = this.__disposer || []; this.t = this.t || {}; + this._configureWasFocused = false; // Track toggle state for Ctrl+Shift+1 // allow commands to go through at any time // hax-store default is only when editor is open to avoid conflicts w/ other UX SuperDaemonInstance.allowedCallback = () => { @@ -1510,7 +1653,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( value: { name: "create-page", machineName: "create-page", - placeholder: "Page title", + placeholder: "Type page title to create page", program: async (input, values) => { // Get reference to site editor UI regardless of how program was accessed const siteEditorUI = @@ -1530,15 +1673,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( args: ["New Page", "sibling"], }, eventName: "super-daemon-element-method", - path: "CMS/action/create/page/blank", - }, - { - title: "Enter a page title above to see more creation options", - icon: "editor:mode-edit", - tags: ["instruction"], - value: { disabled: true }, - eventName: "disabled", - path: "Enter page title in the input field", + path: "CMS/action/create/page", }, ]; } @@ -1552,7 +1687,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( // Add blank page option first results.push({ - title: `Create "${title}" `, + title: `Create ${title}`, icon: "hax:add-page", tags: ["page", "blank", "create"], value: { @@ -1564,27 +1699,11 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( path: "CMS/action/create/page/blank", }); - // Add duplicate current page option - if (currentItem) { - results.push({ - title: `Duplicate as "${title}"`, - icon: "hax:duplicate", - tags: ["page", "duplicate", "create"], - value: { - target: siteEditorUI, - method: "createPageWithTitle", - args: [title, "duplicate"], - }, - eventName: "super-daemon-element-method", - path: "CMS/action/create/page/duplicate", - }); - } - // Add template options if templates are available if (pageTemplates && pageTemplates.length > 0) { pageTemplates.forEach((template, index) => { results.push({ - title: `Create "${title}" with "${template.name}"`, + title: `Create ${template.name} named ${title}`, icon: "hax:templates", tags: ["template", "page", "create"], value: { @@ -1598,6 +1717,22 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }); } + // Add duplicate current page option + if (currentItem) { + results.push({ + title: `Duplicate as ${title}`, + icon: "hax:duplicate", + tags: ["page", "duplicate", "create"], + value: { + target: siteEditorUI, + method: "createPageWithTitle", + args: [title, "duplicate"], + }, + eventName: "super-daemon-element-method", + path: "CMS/action/create/page/duplicate", + }); + } + return results; } catch (error) { console.error("Error in create-page program:", error); @@ -1616,6 +1751,151 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }, }); + // Move Page program + SuperDaemonInstance.defineOption({ + title: "Move Page", + icon: "icons:open-with", + priority: 0, + tags: ["page", "move", "relocate", "parent", "CMS"], + eventName: "super-daemon-run-program", + path: "CMS/action/move/page", + value: { + name: "move-page", + machineName: "move-page", + placeholder: "Type to filter pages", + program: async (input, values) => { + // Get pageId from values or fall back to active item + const pageId = values.pageId || (store.activeItem ? store.activeItem.id : null); + if (!pageId) { + return [ + { + title: "Error: No page selected to move", + icon: "icons:error", + tags: ["error"], + value: { disabled: true }, + eventName: "disabled", + path: "Error occurred", + }, + ]; + } + + // Get the page being moved + const pageToMove = await store.findItem(pageId); + if (!pageToMove) { + return [ + { + title: "Error: Page not found", + icon: "icons:error", + tags: ["error"], + value: { disabled: true }, + eventName: "disabled", + path: "Error occurred", + }, + ]; + } + + // Get all pages in the site + const itemManifest = store.getManifestItems(true); + const results = []; + + // Filter search input + const searchTerm = input ? input.toLowerCase().trim() : ""; + + // Add option to move to root level + if ( + !searchTerm || + searchTerm.includes("root") || + searchTerm.includes("top") || + searchTerm.includes("level") + ) { + results.push({ + title: "Move to Root Level", + icon: "hax:site-map", + tags: ["root", "top", "level"], + value: { + target: globalThis.document.querySelector( + "haxcms-site-editor-ui", + ), + method: "movePageUnderParent", + args: [pageId, null], + }, + eventName: "super-daemon-element-method", + path: "CMS/action/move/page/root", + }); + } + + // Filter and display pages as potential parents + itemManifest.forEach((item) => { + // Don't allow moving under itself or its children + if (item.id === pageId) { + return; + } + + // Check if this item is a child of the page being moved + let isChild = false; + let checkItem = item; + while (checkItem && checkItem.parent) { + if (checkItem.parent === pageId) { + isChild = true; + break; + } + checkItem = itemManifest.find((i) => i.id === checkItem.parent); + } + if (isChild) { + return; + } + + // Apply search filter + if (searchTerm && !item.title.toLowerCase().includes(searchTerm)) { + return; + } + + // Calculate indentation to show hierarchy + let itemBuilder = item; + let distance = ""; + while (itemBuilder && itemBuilder.parent != null) { + itemBuilder = itemManifest.find( + (i) => i.id === itemBuilder.parent, + ); + if (itemBuilder) { + distance = "--" + distance; + } + } + + results.push({ + title: `${distance}${distance ? " " : ""}${item.title}`, + icon: "hax:add-child-page", + tags: ["move", "parent"], + value: { + target: globalThis.document.querySelector( + "haxcms-site-editor-ui", + ), + method: "movePageUnderParent", + args: [pageId, item.id], + }, + eventName: "super-daemon-element-method", + path: `CMS/action/move/page/${item.id}`, + }); + }); + + if (results.length === 0) { + return [ + { + title: "No matching pages found", + icon: "icons:search", + tags: ["empty"], + value: { disabled: true }, + eventName: "disabled", + path: "No results", + }, + ]; + } + + return results; + }, + }, + }); + SuperDaemonInstance.defineOption({ title: "Magic File Wand", icon: "hax:hax2022", @@ -1712,6 +1992,18 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( eventName: "super-daemon-element-method", path: "Image embedded in page", }); + results.push({ + title: `Set as the media for the active page`, + icon: "image:photo-library", + tags: ["agent"], + value: { + target: this, + method: "processFileContentsBasedOnUserDesire", + args: [values, "set-page-media", "image"], + }, + eventName: "super-daemon-element-method", + path: "Image set as page media", + }); break; case "mp4": results.push({ @@ -2143,7 +2435,6 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( "Unsaved changes will be lost if selecting OK, are you sure?", editDetails: "Page details", add: "Add", - editSettings: "Edit settings", source: "Source", viewSource: "Source", confirmHtmlSourceExit: @@ -2159,7 +2450,6 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( addChildPage: "Add child page", clonePage: "Clone page", delete: "Delete page", - shareSite: "Share site", siteSettings: "Site settings", themeSettings: "Theme settings", seoSettings: "SEO settings", @@ -2172,13 +2462,15 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( configure: "Configure", save: "Save", saveAndEdit: "Save & Edit", + unlockPage: "Unlock page", newJourney: "New Journey", accountInfo: "Account Info", - outlineDesigner: "Outline designer", - pageOutline: "Page outline", + outlineDesigner: "Site Outline", + outline: "Outline", + pageOutline: "Structure", more: "More", - siteActions: "Site actions", - insights: "Insights dashboard (beta)", + pageActions: "Page actions", + insights: "Insights dashboard", merlin: "Merlin", summonMerlin: "Summon Merlin", logOut: "Log out", @@ -2190,8 +2482,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( this.painting = true; this.pageAllowed = false; this.editMode = false; - this.__editIcon = "hax:page-edit"; - this.icon = "hax:site-settings"; + this.__editIcon = "icons:create"; this.manifestEditMode = false; this.backLink = "../../"; this.activeTagName = ""; @@ -2356,10 +2647,6 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( import( "@haxtheweb/haxcms-elements/lib/core/haxcms-outline-editor-dialog.js" ); - // prettier-ignore - import( - "@haxtheweb/haxcms-elements/lib/core/haxcms-share-dialog.js" - ); }, 0); } displayConsoleWarning() { @@ -2387,6 +2674,21 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( } } + /** + * Check if a platform capability is allowed + * Defaults to true if not explicitly set to false in platformConfig + * @param {string} capability - Capability name (e.g., 'delete', 'addPage', 'outlineDesigner') + * @returns {boolean} Whether the capability is allowed + */ + platformAllows(capability) { + if (!this.platformConfig || typeof this.platformConfig !== "object") { + return true; // No restrictions if no platform config + } + // If the capability is not defined, default to true (allowed) + // If it is defined, use its value + return this.platformConfig[capability] !== false; + } + closeMenu() { this.userMenuOpen = false; } @@ -2417,24 +2719,32 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( > - +
    + + - + label="${this.t.outline} • Ctrl⇧2" + @click="${this._outlineButtonTap}" + > + - - - - - - + ?disabled="${!this.canUndo}" + @click="${this.haxButtonOp}" + label="${this.t.undo} • Ctrl Z" + data-event="undo" + id="undo" + ?hidden="${!this.editMode}" + voice-command="undo" + > + + @click="${this.haxButtonOp}" + ?disabled="${!this.canRedo}" + ?hidden="${!this.editMode}" + label="${this.t.redo} • Ctrl⇧Z" + data-event="redo" + id="redo" + voice-command="redo" + icon-position="${this.getIconPosition(this.responsiveSize)}" + > + - - @@ -2580,128 +2857,89 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( icon="hax:newspaper" id="content-map" class="top-bar-button" - label="${this.t.pageOutline}" + label="${this.t.pageOutline} • Ctrl⇧3" voice-command="select content outline (menu)" - toggles @click="${this.haxButtonOp}" - ?toggled="${this.trayDetail === "content-map"}" + ?active="${this.trayDetail === "content-map"}" icon-position="${this.getIconPosition(this.responsiveSize)}" - show-text-label > - - - - - - - - - - - - - - - - - - + + + + + + + - +
    - ${this.userName} + ${this.userName}
    @@ -2754,11 +2998,20 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( elevation="1" class="soundToggle" @click="${this.soundToggle}" + aria-label="Toggle sound effects ${this.soundIcon && + this.soundIcon.indexOf("Full") !== -1 + ? "off" + : "on"}" + aria-pressed="${this.soundIcon && + this.soundIcon.indexOf("Full") !== -1 + ? "true" + : "false"}" > @@ -2881,9 +3134,11 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( break; case "media-program": store.playSound("click"); - SuperDaemonInstance.runProgram("sources"); - SuperDaemonInstance.open(); - HAXStore.haxTray.collapsed = false; + SuperDaemonInstance.waveWand( + ["sources", "/"], + this.shadowRoot.querySelector("#merlin"), + null, + ); break; case "content-edit": case "content-map": @@ -2944,10 +3199,6 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( this.backText = globalThis.appSettings.backText; } let ary = [ - { - varPath: "deleteNodePath", - selector: "#deletebutton", - }, { varPath: "saveNodePath", selector: "#editbutton", @@ -3136,18 +3387,6 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( eventName: "super-daemon-element-method", path: "CMS/action/site/settings/author", }); - SuperDaemonInstance.defineOption({ - title: this.t.shareSite, - icon: "social:share", - tags: ["CMS", "share"], - value: { - target: this, - method: "_shareButtonTap", - }, - context: ["logged-in", "CMS", "HAX"], - eventName: "super-daemon-element-method", - path: "CMS/action/share", - }); SuperDaemonInstance.defineOption({ title: this.t.styleGuide, icon: "lrn:palette", @@ -3275,6 +3514,112 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }, }); // change theme program + // Welcome to HAX program - shows 5 most common operations for new users + SuperDaemonInstance.defineOption({ + title: "Welcome to HAX", + icon: "hax:hax2022", + tags: [ + "welcome", + "tutorial", + "getting started", + "onboarding", + "help", + "guide", + ], + eventName: "super-daemon-run-program", + path: "CMS/welcome", + context: ["CMS", "logged-in"], + priority: 2000, // Low priority to appear at bottom + value: { + name: "Welcome to HAX", + machineName: "welcome", + context: "CMS", + program: async (input, values) => { + // Four most common operations for new users (removed create-page) + return [ + { + title: "Edit this page", + icon: "hax:page-edit", + tags: ["welcome", "common", "operation"], + value: { + target: this, + method: "executeWelcomeAction", + args: ["edit-page"], + }, + eventName: "super-daemon-element-method", + context: ["CMS"], + path: "CMS/welcome/edit-page", + }, + { + title: "Create a new page", + icon: "hax:add-page", + tags: ["welcome", "common", "operation"], + value: { + target: this, + method: "executeWelcomeAction", + args: ["create-page"], + }, + eventName: "super-daemon-element-method", + context: ["CMS"], + path: "CMS/welcome/create-page", + }, + { + title: "Upload a file", + icon: "file-upload", + tags: ["welcome", "common", "operation"], + value: { + target: this, + method: "executeWelcomeAction", + args: ["upload-file"], + }, + eventName: "super-daemon-element-method", + context: ["CMS"], + path: "CMS/welcome/upload-file", + }, + { + title: "Edit site outline", + icon: "hax:site-map", + tags: ["welcome", "common", "operation"], + value: { + target: this, + method: "executeWelcomeAction", + args: ["outline-designer"], + }, + eventName: "super-daemon-element-method", + context: ["CMS"], + path: "CMS/welcome/outline-designer", + }, + { + title: "Change site settings", + icon: "hax:site-settings", + tags: ["welcome", "common", "operation"], + value: { + target: this, + method: "executeWelcomeAction", + args: ["site-settings"], + }, + eventName: "super-daemon-element-method", + context: ["CMS"], + path: "CMS/welcome/site-settings", + }, + { + title: "Don't show this welcome again", + icon: "icons:visibility-off", + tags: ["welcome", "dismiss", "settings"], + value: { + target: this, + method: "dismissWelcomeProgram", + args: [], + }, + eventName: "super-daemon-element-method", + context: ["CMS"], + path: "CMS/welcome/dismiss", + }, + ]; + }, + }, + }); + SuperDaemonInstance.defineOption({ title: "Change theme temporarily", icon: "image:style", @@ -3503,6 +3848,98 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }, }); + // Edit Title Program + SuperDaemonInstance.defineOption({ + title: "Edit title", + icon: "editor:title", + tags: ["CMS", "edit", "title", "metadata"], + eventName: "super-daemon-run-program", + path: "CMS/edit/title", + context: ["CMS"], + voice: "edit title", + value: { + name: "Edit title", + machineName: "edit-title", + placeholder: "Enter new title", + program: async (input, values) => { + const { createEditTitleProgram } = await import( + "./utils/EditTitleProgram.js" + ); + const editTitleProgram = createEditTitleProgram(this); + return await editTitleProgram(input, values); + }, + }, + }); + + // Edit Description Program + SuperDaemonInstance.defineOption({ + title: "Edit description", + icon: "editor:format-quote", + tags: ["CMS", "edit", "description", "metadata"], + eventName: "super-daemon-run-program", + path: "CMS/edit/description", + context: ["CMS"], + voice: "edit description", + value: { + name: "Edit description", + machineName: "edit-description", + placeholder: "Enter new description", + program: async (input, values) => { + const { createEditDescriptionProgram } = await import( + "./utils/EditDescriptionProgram.js" + ); + const editDescriptionProgram = createEditDescriptionProgram(this); + return await editDescriptionProgram(input, values); + }, + }, + }); + + // Edit Slug Program + SuperDaemonInstance.defineOption({ + title: "Edit slug", + icon: "editor:insert-link", + tags: ["CMS", "edit", "slug", "url", "metadata"], + eventName: "super-daemon-run-program", + path: "CMS/edit/slug", + context: ["CMS"], + voice: "edit slug", + value: { + name: "Edit slug", + machineName: "edit-slug", + placeholder: "Enter new slug (URL path)", + program: async (input, values) => { + const { createEditSlugProgram } = await import( + "./utils/EditSlugProgram.js" + ); + const editSlugProgram = createEditSlugProgram(this); + return await editSlugProgram(input, values); + }, + }, + }); + + // Edit Tags Program + SuperDaemonInstance.defineOption({ + title: "Edit tags", + icon: "icons:label", + tags: ["CMS", "edit", "tags", "metadata"], + eventName: "super-daemon-run-program", + path: "CMS/edit/tags", + context: ["CMS"], + voice: "edit tags", + value: { + name: "Edit tags", + machineName: "edit-tags", + placeholder: "Enter tags separated by commas", + program: async (input, values) => { + const { createEditTagsProgram } = await import( + "./utils/EditTagsProgram.js" + ); + const editTagsProgram = createEditTagsProgram(this); + return await editTagsProgram(input, values); + }, + }, + }); + // Core site navigation programs - available regardless of theme SuperDaemonInstance.defineOption({ title: "Go to page in this site", @@ -3590,6 +4027,83 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }, }); + // Keyboard shortcuts program - displays all shortcuts and executes them on click + SuperDaemonInstance.defineOption({ + title: "Keyboard shortcuts", + icon: "hardware:keyboard", + tags: ["help", "shortcuts", "keyboard", "reference"], + eventName: "super-daemon-run-program", + path: "CMS/help/keyboard-shortcuts", + context: ["CMS"], + value: { + name: "Keyboard shortcuts", + context: ["CMS"], + program: async (input) => { + const shortcuts = + HAXCMSKeyboardShortcutsInstance.getShortcutsForDisplay(); + const results = []; + + shortcuts.forEach((shortcut) => { + // Filter by search input + if ( + input === "" || + shortcut.description + .toLowerCase() + .includes(input.toLowerCase()) || + shortcut.label.toLowerCase().includes(input.toLowerCase()) + ) { + results.push({ + title: `${shortcut.description} • ${shortcut.label}`, + icon: "hardware:keyboard", + tags: ["shortcut", "keyboard", shortcut.context], + value: { + shortcutKey: shortcut.key, + }, + context: ["CMS"], + eventName: "execute-keyboard-shortcut", + path: "CMS/help/keyboard-shortcuts", + }); + } + }); + + return results; + }, + }, + }); + + // Listen for keyboard shortcut execution from Merlin + this.addEventListener("execute-keyboard-shortcut", (e) => { + const shortcutKey = e.detail.value.shortcutKey; + // Parse the shortcut key to get individual components + const parts = shortcutKey.split("+"); + const key = parts[parts.length - 1]; + const ctrl = parts.includes("Ctrl"); + const shift = parts.includes("Shift"); + const alt = parts.includes("Alt"); + const meta = parts.includes("Meta"); + + // Get the shortcut and execute its callback + const shortcut = HAXCMSKeyboardShortcutsInstance.getShortcut( + key, + ctrl, + shift, + alt, + meta, + ); + if (shortcut && shortcut.callback) { + // Create a synthetic keyboard event + const syntheticEvent = new KeyboardEvent("keydown", { + key: key, + ctrlKey: ctrl, + shiftKey: shift, + altKey: alt, + metaKey: meta, + bubbles: true, + }); + shortcut.callback(syntheticEvent); + } + }); + this.updateAvailableButtons(); // load user data this.dispatchEvent( @@ -3600,6 +4114,9 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( detail: true, }), ); + + // Auto-trigger welcome program for first-time users + this.checkAndTriggerWelcomeProgram(); } // enable lab experiments enableLabExperiments() { @@ -3611,6 +4128,118 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( ); } + // Check if user should see welcome program and trigger it + checkAndTriggerWelcomeProgram() { + // Only trigger if user is logged in and hasn't seen the welcome before + if ( + store.isLoggedIn && + !UserScaffoldInstance.readMemory("hasSeenHaxWelcome") + ) { + // Wait for the store and UI to be fully ready + const triggerWelcome = () => { + if ( + store.appReady && + this.shadowRoot && + this.shadowRoot.querySelector("#merlin") + ) { + // Auto-trigger the welcome program using waveWand for mini mode + SuperDaemonInstance.waveWand( + [ + "", // empty search to show all results + "CMS", // context + { operation: "welcome" }, // values + "welcome", // program machine name + "Welcome to HAX", // display name + ], + this.shadowRoot.querySelector("#merlin"), // target for mini mode + "magic", // sound + ); + + // Show a toast to let the user know Merlin is here to help + store.toast( + "👋 Welcome to HAX! Merlin is here to help you get started", + 8000, + { + hat: "knight", + }, + ); + } else { + // Retry after a short delay if not ready yet + setTimeout(triggerWelcome, 500); + } + }; + + // Give some time for everything to initialize + setTimeout(triggerWelcome, 2000); + } + } + + // Method to dismiss the welcome program permanently + dismissWelcomeProgram() { + // Mark that the user has seen and dismissed the welcome program + UserScaffoldInstance.writeMemory("hasSeenHaxWelcome", true, "long"); + + // Close Merlin + SuperDaemonInstance.close(); + + // Show confirmation toast + store.toast( + "Welcome program dismissed. You can always access Merlin by pressing Alt+Shift or clicking the search bar.", + 5000, + { + hat: "good", + }, + ); + } + + // Method to execute welcome program actions + executeWelcomeAction(actionType) { + // Execute the appropriate action based on type + switch (actionType) { + case "create-page": + // Trigger the add page button + SuperDaemonInstance.close(); + setTimeout(() => { + this.shadowRoot.querySelector("#addpagebutton").HAXCMSButtonClick(); + }, 100); + break; + case "edit-page": + // Trigger the edit page button + this._editButtonTap(); + break; + + case "upload-file": + // Use waveWand to trigger the file upload program + SuperDaemonInstance.waveWand( + [ + "", // empty search to show all file options + "/", // context for file operations + { operation: "file-upload" }, + "hax-agent", + "File Agent", + ], + this.shadowRoot.querySelector("#merlin"), + "coin2", + ); + break; + + case "outline-designer": + // Trigger the Site Outline + this._outlineButtonTap(); + break; + + case "site-settings": + // Trigger the site settings + this._manifestButtonTap({ + target: this.shadowRoot.querySelector("#manifestbtn"), + }); + break; + + default: + console.warn("Unknown welcome action type:", actionType); + } + } + // enable view only mode enableViewOnlyMode() { // Set ViewOnlyMode in UserContext as a session variable @@ -3704,6 +4333,13 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( return _downloadSiteArchive.call(this); } + async _exportSiteAsSkeleton(manifest, title, baseUrl) { + const { _exportSiteAsSkeleton } = await import( + "./utils/ExportSiteProgram.js" + ); + return _exportSiteAsSkeleton.call(this, manifest, title, baseUrl); + } + // Utility methods from both export programs _downloadFile(content, filename, mimeType = "text/plain") { const blob = new Blob([content], { type: mimeType }); @@ -3734,19 +4370,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( } } if (propName === "responsiveSize") { - if (["xs", "sm", "md"].includes(this[propName])) { - if (this.editMode) { - this.__editText = this.t.save; - } else { - this.__editText = this.t.edit; - } - } else { - if (this.editMode) { - this.__editText = this.t.save; - } else { - this.__editText = this.t.edit; - } - } + this._updateEditButtonLabel(); } if (propName == "editMode") { if (this.editMode) { @@ -3754,6 +4378,8 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( } else { this.rpgHat = "none"; } + // Update edit button label when mode changes + this._updateEditButtonLabel(); if (oldValue !== undefined) { SuperDaemonInstance.close(); } @@ -3877,11 +4503,20 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( type: String, attribute: "active-title", }, + activeItem: { + type: Object, + }, manifest: { type: Object, }, - icon: { + /** + * Current active tray detail (content-edit, content-add, content-map, view-source) + * mirrored from HAXStore.haxTray.trayDetail so the top bar can expose state + */ + trayDetail: { type: String, + attribute: "tray-detail", + reflect: true, }, /** * Whether we're currently on an internal route @@ -3889,11 +4524,30 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( onInternalRoute: { type: Boolean, }, + /** + * Platform configuration from manifest.metadata.platform + * Controls additional restrictions on site editing capabilities + */ + platformConfig: { + type: Object, + }, }; } connectedCallback() { super.connectedCallback(); + // Register keyboard shortcuts + this._registerKeyboardShortcuts(); + HAXCMSKeyboardShortcutsInstance.enable(); + + // Mirror HAX tray detail into this element so we can expose and style active state. + // IMPORTANT: use the observable HAXStore.trayDetail, not haxTray.trayDetail, + // so MobX actually tracks and updates this value. + autorun((reaction) => { + this.trayDetail = toJS(HAXStore.trayDetail); + this.__disposer.push(reaction); + }); + autorun((reaction) => { if (store.userData) { this.userName = toJS(store.userData.userName); @@ -3904,13 +4558,39 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( this.__disposer.push(reaction); }); autorun((reaction) => { - this.editMode = toJS(store.editMode); + const previousEditMode = this.editMode; + const newEditMode = toJS(store.editMode); + this.editMode = newEditMode; UserScaffoldInstance.writeMemory("editMode", this.editMode); + // When we first enter edit mode and there is an active node selected, + // prefer the Configure tab over Blocks as the default tray panel. + if ( + !previousEditMode && + newEditMode && + HAXStore.activeNode && + HAXStore.activeNode.tagName + ) { + HAXStore.trayDetail = "content-edit"; + if (HAXStore.haxTray) { + HAXStore.haxTray.trayDetail = "content-edit"; + HAXStore.haxTray.collapsed = false; + } + } this.__disposer.push(reaction); }); autorun((reaction) => { this.manifest = toJS(store.manifest); - this.icon = "hax:site-settings"; + // Extract platform configuration from manifest metadata + if ( + this.manifest && + this.manifest.metadata && + this.manifest.metadata.platform + ) { + this.platformConfig = toJS(this.manifest.metadata.platform); + } else { + // Default to empty object if no platform config exists + this.platformConfig = {}; + } this.__disposer.push(reaction); }); autorun((reaction) => { @@ -3919,13 +4599,16 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }); autorun((reaction) => { const activeItem = toJS(store.activeItem); + this.activeItem = activeItem; // update buttons to match since we got a state response this.updateAvailableButtons(); if (activeItem && activeItem.id) { this.activeTitle = activeItem.title; this.onInternalRoute = activeItem._internalRoute || false; // Use the store method to determine if editing is allowed - store.pageAllowed = store.currentRouteSupportsHaxEditor(); + const supportsEditor = store.currentRouteSupportsHaxEditor(); + // Show the button if editor is supported, regardless of lock status + store.pageAllowed = supportsEditor; } else { this.onInternalRoute = false; store.pageAllowed = false; @@ -3934,15 +4617,307 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }); } disconnectedCallback() { + // Unregister keyboard shortcuts + HAXCMSKeyboardShortcutsInstance.disable(); + for (var i in this.__disposer) { this.__disposer[i].dispose(); } super.disconnectedCallback(); } + + /** + * Update edit button label with appropriate shortcut + */ + _updateEditButtonLabel() { + if (this.editMode) { + this.__editText = `${this.t.save} • Ctrl⇧S`; + } else { + this.__editText = `${this.t.edit} • Ctrl⇧E`; + } + } + + /** + * Register all keyboard shortcuts for HAXcms + */ + _registerKeyboardShortcuts() { + // Ctrl+Shift+S - Save (only when in edit mode) + HAXCMSKeyboardShortcutsInstance.register({ + key: "S", + ctrl: true, + shift: true, + callback: (e) => { + this._editButtonTap(e); + }, + condition: () => store.isLoggedIn && this.pageAllowed && this.editMode, + description: "Save page and exit edit mode", + context: "edit", + }); + + // Ctrl+Shift+E - Edit (only when NOT in edit mode) + HAXCMSKeyboardShortcutsInstance.register({ + key: "E", + ctrl: true, + shift: true, + callback: (e) => { + this._editButtonTap(e); + }, + condition: () => store.isLoggedIn && this.pageAllowed && !this.editMode, + description: "Enter edit mode", + context: "view", + }); + + // Ctrl+Shift+W - Save and Edit (when in edit mode) + HAXCMSKeyboardShortcutsInstance.register({ + key: "W", + ctrl: true, + shift: true, + callback: (e) => { + this._saveAndEditButtonTap(e); + }, + condition: () => store.isLoggedIn && this.pageAllowed && this.editMode, + description: "Save and continue editing", + context: "edit", + }); + + // Ctrl+Shift+/ - Cancel editing (register as / not ?) + HAXCMSKeyboardShortcutsInstance.register({ + key: "/", + ctrl: true, + shift: true, + callback: (e) => { + this._cancelButtonTap(e); + }, + condition: () => store.isLoggedIn && this.editMode, + description: "Cancel editing", + context: "edit", + }); + + + + + // Ctrl+Shift+V - Toggle view source mode in HAX + HAXCMSKeyboardShortcutsInstance.register({ + key: "V", + ctrl: true, + shift: true, + callback: (e) => { + if (HAXStore.haxTray) { + if (HAXStore.haxTray.trayDetail === "view-source") { + HAXStore.haxTray.collapsed = true; + } else { + HAXStore.haxTray.trayDetail = "view-source"; + HAXStore.haxTray.shadowRoot + .querySelector("#view-source") + .openSource(); + HAXStore.haxTray.collapsed = false; + } + } + }, + condition: () => store.isLoggedIn && this.editMode, + description: "Toggle view source mode", + context: "edit", + }); + + // Ctrl+Shift+B - Open block browser in HAX + HAXCMSKeyboardShortcutsInstance.register({ + key: "B", + ctrl: true, + shift: true, + callback: (e) => { + if (HAXStore.haxTray) { + if (HAXStore.haxTray.trayDetail === "content-add") { + HAXStore.haxTray.collapsed = !HAXStore.haxTray.collapsed; + } else { + HAXStore.haxTray.trayDetail = "content-add"; + HAXStore.haxTray.collapsed = false; + } + } + }, + condition: () => store.isLoggedIn && this.editMode, + description: "Open block browser", + context: "edit", + }); + + // Numbered shortcuts - contextual based on edit mode + // Ctrl+Shift+1 - Configure panel (edit) OR Add page (non-edit) + HAXCMSKeyboardShortcutsInstance.register({ + key: "1", + ctrl: true, + shift: true, + callback: (e) => { + if (this.editMode) { + // Edit mode: Open configure panel + if (HAXStore.haxTray) { + HAXStore.haxTray.trayDetail = "content-edit"; + HAXStore.haxTray.collapsed = false; + } + } else { + // Non-edit mode: Add page + if (this.platformAllows("addPage")) { + const addButton = this.shadowRoot.querySelector("#addpagebutton"); + if (addButton) { + addButton.HAXCMSButtonClick(); + } + } + } + }, + condition: () => store.isLoggedIn, + description: "Configure panel (edit) / Add page (view)", + context: "global", + }); + + // Ctrl+Shift+2 - Blocks browser (edit) OR Site outline (non-edit) + HAXCMSKeyboardShortcutsInstance.register({ + key: "2", + ctrl: true, + shift: true, + callback: (e) => { + if (this.editMode) { + // Edit mode: Blocks browser + if (HAXStore.haxTray) { + if (HAXStore.haxTray.trayDetail === "content-add") { + HAXStore.haxTray.collapsed = !HAXStore.haxTray.collapsed; + } else { + HAXStore.haxTray.trayDetail = "content-add"; + HAXStore.haxTray.collapsed = false; + } + } + } else { + // Non-edit mode: Site outline + if (this.platformAllows("outlineDesigner")) { + this._outlineButtonTap(e); + } + } + }, + condition: () => store.isLoggedIn, + description: "Blocks browser (edit) / Site outline (view)", + context: "global", + }); + + // Ctrl+Shift+3 - Page structure (edit) OR Style guide (non-edit) + HAXCMSKeyboardShortcutsInstance.register({ + key: "3", + ctrl: true, + shift: true, + callback: (e) => { + if (this.editMode) { + // Edit mode: Page structure + if (HAXStore.haxTray) { + if (HAXStore.haxTray.trayDetail === "content-map") { + HAXStore.haxTray.collapsed = !HAXStore.haxTray.collapsed; + } else { + HAXStore.haxTray.trayDetail = "content-map"; + HAXStore.haxTray.collapsed = false; + } + } + } else { + // Non-edit mode: Style guide + if (this.platformAllows("styleGuide")) { + this._styleGuideButtonTap(e); + } + } + }, + condition: () => store.isLoggedIn, + description: "Page structure (edit) / Style guide (view)", + context: "global", + }); + + // Ctrl+Shift+4 - View source (edit) OR Insights dashboard (non-edit) + HAXCMSKeyboardShortcutsInstance.register({ + key: "4", + ctrl: true, + shift: true, + callback: (e) => { + if (this.editMode) { + // Edit mode: View source + if (HAXStore.haxTray) { + if (HAXStore.haxTray.trayDetail === "view-source") { + HAXStore.haxTray.collapsed = true; + } else { + HAXStore.haxTray.trayDetail = "view-source"; + HAXStore.haxTray.shadowRoot + .querySelector("#view-source") + .openSource(); + HAXStore.haxTray.collapsed = false; + } + } + } else { + // Non-edit mode: Insights dashboard + if (this.platformAllows("insights")) { + this._insightsButtonTap(e); + } + } + }, + condition: () => store.isLoggedIn, + description: "View source (edit) / Insights dashboard (view)", + context: "global", + }); + + // Ctrl+Shift+5 - Media browser (edit) OR Site settings (non-edit) + HAXCMSKeyboardShortcutsInstance.register({ + key: "5", + ctrl: true, + shift: true, + callback: (e) => { + if (this.editMode) { + // Edit mode: Media browser + SuperDaemonInstance.waveWand( + ["sources", "/"], + this.shadowRoot.querySelector("#merlin"), + null, + ); + } else { + // Non-edit mode: Site settings + if (this.platformAllows("manifest")) { + this._manifestButtonTap(e); + } + } + }, + condition: () => store.isLoggedIn, + description: "Media browser (edit) / Site settings (view)", + context: "global", + }); + + // Ctrl+Shift+6 - Super Daemon (alternative shortcut) + HAXCMSKeyboardShortcutsInstance.register({ + key: "6", + ctrl: true, + shift: true, + callback: (e) => { + SuperDaemonInstance.waveWand( + ["", "*"], + this.shadowRoot.querySelector("#merlin"), + null, + ); + }, + condition: () => store.isLoggedIn, + description: "Open Merlin (Super Daemon)", + context: "global", + }); + } + /** * toggle state on button tap */ _editButtonTap(e) { + // Check if the active page is locked + const activeItem = toJS(store.activeItem); + if ( + activeItem && + activeItem.metadata && + activeItem.metadata.locked && + !this.editMode + ) { + store.toast( + "This page is locked. Click the lock button to unlock it before editing.", + 3000, + { hat: "error" }, + ); + store.playSound("error"); + return false; + } + if (this.editMode && HAXStore.haxTray.trayDetail === "view-source") { if (!globalThis.confirm(this.t.confirmHtmlSourceExit)) { return false; @@ -4008,7 +4983,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( title: this.t.insights, styles: { "--simple-modal-titlebar-background": "black", - "--simple-modal-titlebar-color": "var(--hax-ui-background-color)", + "--simple-modal-titlebar-color": "light-dark(black, white)", "--simple-modal-width": "94vw", "--simple-modal-min-width": "300px", "--simple-modal-z-index": "100000000", @@ -4084,10 +5059,10 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( composed: true, cancelable: false, detail: { - title: "Are you sure you want to delete this page?", + title: "Permanently delete this page?", styles: { "--simple-modal-titlebar-background": "black", - "--simple-modal-titlebar-color": "var(--hax-ui-background-color)", + "--simple-modal-titlebar-color": "light-dark(black, white)", "--simple-modal-width": "25vw", "--simple-modal-min-width": "300px", "--simple-modal-z-index": "100000000", @@ -4096,7 +5071,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( "--simple-modal-titlebar-height": "80px", }, elements: { content: c, buttons: b }, - invokedBy: this.shadowRoot.querySelector("#deletebutton"), + invokedBy: this, clone: false, modal: true, }, @@ -4119,35 +5094,181 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( this.dispatchEvent(evt); } /** - * toggle share button + * Edit title via Merlin program */ - _shareButtonTap(e) { + _editTitlePrompt(e) { + const activeItem = toJS(store.activeItem); + if (!activeItem || !activeItem.id) { + return; + } + const currentTitle = activeItem.title || ""; + const SuperDaemonInstance = + globalThis.SuperDaemonManager.requestAvailability(); store.playSound("click"); - const evt = new CustomEvent("simple-modal-show", { - bubbles: true, - composed: true, - cancelable: false, - detail: { - title: this.t.shareSite, - styles: { - "--simple-modal-titlebar-background": "black", - "--simple-modal-titlebar-color": "var(--hax-ui-background-color)", - "--simple-modal-width": "55vw", - "--simple-modal-min-width": "300px", - "--simple-modal-z-index": "100000000", - "--simple-modal-height": "50vh", - "--simple-modal-min-height": "300px", - "--simple-modal-titlebar-height": "80px", + SuperDaemonInstance.waveWand([ + currentTitle, // Pre-fill input with current title + "/", + {}, + "edit-title", + "Edit title", + ]); + } + /** + * Edit description via Merlin program + */ + _editDescriptionPrompt(e) { + const activeItem = toJS(store.activeItem); + if (!activeItem || !activeItem.id) { + return; + } + const currentDescription = activeItem.description || ""; + const SuperDaemonInstance = + globalThis.SuperDaemonManager.requestAvailability(); + store.playSound("click"); + SuperDaemonInstance.waveWand([ + currentDescription, + "/", + {}, + "edit-description", + "Edit description", + "", + ]); + } + /** + * Toggle lock status + */ + _toggleLockStatus(e) { + const activeItem = toJS(store.activeItem); + if (!activeItem || !activeItem.id) return; + + const isLocked = activeItem.metadata && activeItem.metadata.locked; + store.playSound("click"); + globalThis.dispatchEvent( + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: activeItem.id, + operation: "setLocked", + locked: !isLocked, }, - elements: { - content: globalThis.document.createElement("haxcms-share-dialog"), + }), + ); + } + /** + * Duplicate page prompt + */ + _duplicatePagePrompt(e) { + const activeItem = toJS(store.activeItem); + if (!activeItem || !activeItem.id) return; + + const newTitle = globalThis.prompt( + "Enter title for duplicated page:", + `${activeItem.title} (copy)`, + ); + if (newTitle && newTitle.trim() !== "") { + store.playSound("click"); + const SuperDaemonInstance = + globalThis.SuperDaemonManager.requestAvailability(); + // Use the create-page program with duplicate option + SuperDaemonInstance.waveWand([ + newTitle, + "/", + {}, + "create-page", + "Add Page", + ]); + } + } + /** + * Edit slug prompt + */ + _editSlugPrompt(e) { + const activeItem = toJS(store.activeItem); + if (!activeItem || !activeItem.id) { + return; + } + const currentSlug = activeItem.slug || ""; + const SuperDaemonInstance = + globalThis.SuperDaemonManager.requestAvailability(); + store.playSound("click"); + SuperDaemonInstance.waveWand([ + currentSlug, + "/", + {}, + "edit-slug", + "Edit slug", + "", + ]); + } + /** + * Move page program from menu + */ + _movePageProgramFromMenu(e) { + const item = toJS(store.activeItem); + if (!item || !item.id) { + return; + } + const SuperDaemonInstance = + globalThis.SuperDaemonManager.requestAvailability(); + store.playSound("click"); + SuperDaemonInstance.waveWand([ + "", + "/", + { pageId: item.id }, + "move-page", + "Move Page", + "", + ]); + } + /** + * Toggle locked status + */ + _toggleLockedStatus(e) { + const item = toJS(store.activeItem); + if (!item || !item.id) { + return; + } + const currentStatus = item.metadata && item.metadata.locked ? true : false; + const newStatus = !currentStatus; + store.playSound("click"); + globalThis.dispatchEvent( + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: item.id, + operation: "setLocked", + locked: newStatus, }, - invokedBy: this.shadowRoot.querySelector("#sharebutton"), - clone: false, - modal: false, - }, - }); - globalThis.dispatchEvent(evt); + }), + ); + } + /** + * Toggle published status + */ + _togglePublishedStatus(e) { + const item = toJS(store.activeItem); + if (!item || !item.id) { + return; + } + const currentStatus = item.metadata && item.metadata.published !== false; + const newStatus = !currentStatus; + store.playSound("click"); + globalThis.dispatchEvent( + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: item.id, + operation: "setPublished", + published: newStatus, + }, + }), + ); } /** * toggle state on button tap @@ -4162,7 +5283,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( title: this.t.outlineDesigner, styles: { "--simple-modal-titlebar-background": "black", - "--simple-modal-titlebar-color": "var(--hax-ui-background-color)", + "--simple-modal-titlebar-color": "light-dark(black, white)", "--simple-modal-z-index": "100000000", "--simple-modal-titlebar-height": "80px", "--simple-modal-width": "85vw", @@ -4185,7 +5306,7 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( _addButtonTap() { store.playSound("click"); setTimeout(() => { - globalThis.location = this.backLink + "createSite-step-1"; + globalThis.location = this.backLink; }, 100); } /** @@ -4207,10 +5328,10 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( composed: true, cancelable: false, detail: { - title: this.t.editSettings, + title: this.t.siteSettings, styles: { "--simple-modal-titlebar-background": "black", - "--simple-modal-titlebar-color": "var(--hax-ui-background-color)", + "--simple-modal-titlebar-color": "light-dark(black, white)", "--simple-modal-z-index": "100000000", "--simple-modal-titlebar-height": "80px", "--simple-modal-width": "85vw", @@ -4249,15 +5370,14 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( this._originalContent = await HAXStore.activeHaxBody.haxToContent(); }, 100); this.__editIcon = "icons:save"; - this.__editText = this.t.save; SuperDaemonInstance.appendContext("HAX"); SuperDaemonInstance.removeContext("CMS"); } else { - this.__editIcon = "hax:page-edit"; - this.__editText = this.t.edit; + this.__editIcon = "icons:create"; SuperDaemonInstance.appendContext("CMS"); SuperDaemonInstance.removeContext("HAX"); } + this._updateEditButtonLabel(); if (typeof oldValue !== typeof undefined) { store.editMode = newValue; // force tray status to be the opposite of the editMode @@ -4293,6 +5413,51 @@ class HAXCMSSiteEditorUI extends HAXCMSThemeParts( }), ); } + + /** + * Handle Konami Code activation - dynamically load cheat codes + */ + async _konamiCodeActivated(e) { + if (!this.konamiCodeActivated) { + this.konamiCodeActivated = true; + + try { + // Dynamically import the cheat codes module for performance + const cheatModule = await import("./haxcms-cheat-codes.js"); + + // Add all cheat code methods to this instance + cheatModule.addCheatCodeMethods(this, SuperDaemonInstance); + + // Define all cheat code programs in Merlin + cheatModule.defineCheatCodes(this, SuperDaemonInstance); + + // Show notification that cheat codes are now available + HAXStore.toast( + "🎮 Cheat codes unlocked! Check Merlin for new programs.", + ); + + // Close Merlin first to reset state, then reopen after delay + SuperDaemonInstance.close(); + + setTimeout(() => { + SuperDaemonInstance.mini = true; + SuperDaemonInstance.wand = true; + SuperDaemonInstance.activeNode = + this.shadowRoot.querySelector("#merlin"); + SuperDaemonInstance.open(); + // Force refresh of items to include new cheat codes + SuperDaemonInstance.items = SuperDaemonInstance.filterItems( + SuperDaemonInstance.allItems, + SuperDaemonInstance.context, + ); + SuperDaemonInstance.runProgram("", "*"); + }, 100); + } catch (error) { + console.error("🎮 Failed to load cheat codes:", error); + HAXStore.toast("🎮 Failed to unlock cheat codes"); + } + } + } } globalThis.customElements.define(HAXCMSSiteEditorUI.tag, HAXCMSSiteEditorUI); export { HAXCMSSiteEditorUI }; diff --git a/elements/haxcms-elements/lib/core/haxcms-site-editor.js b/elements/haxcms-elements/lib/core/haxcms-site-editor.js index 5cf358574a..2baba06f0b 100644 --- a/elements/haxcms-elements/lib/core/haxcms-site-editor.js +++ b/elements/haxcms-elements/lib/core/haxcms-site-editor.js @@ -49,20 +49,9 @@ class HAXCMSSiteEditor extends LitElement { this.manifest = toJS(store.manifest); this.__disposer.push(reaction); }); + // Sync activeItem directly from store via MobX for proper state management autorun((reaction) => { this.activeItem = toJS(store.activeItem); - const baseUrl = toJS(store.location.baseUrl); - // account for haxiam vs non-haxiam - if (baseUrl && this.activeItem && this.activeItem.location) { - const sitePathAry = baseUrl.replace("/sites", "").split("/"); - if (sitePathAry.length === 5) { - HAXStore.revisionHistoryLink = `/${sitePathAry[2]}/gitlist/${sitePathAry[3]}/logpatch/master/${this.activeItem.location}`; - } else if (sitePathAry.length === 4) { - HAXStore.revisionHistoryLink = `/${sitePathAry[1]}/gitlist/${sitePathAry[2]}/logpatch/master/${this.activeItem.location}`; - } else if (sitePathAry.length === 3) { - HAXStore.revisionHistoryLink = `/gitlist/${sitePathAry[1]}/logpatch/master/${this.activeItem.location}`; - } - } this.__disposer.push(reaction); }); } @@ -149,6 +138,17 @@ class HAXCMSSiteEditor extends LitElement { @last-error-changed="${this.lastErrorChanged}" @last-response-changed="${this.__deleteNodeResponseChanged}" > + { + if (allowedBlocks.includes(tagName)) { + filteredAutoloader[tagName] = filteredAppStore.autoloader[tagName]; + } + }); + + filteredAppStore.autoloader = filteredAutoloader; + } + + return filteredAppStore; + } + updated(changedProperties) { changedProperties.forEach((oldValue, propName) => { if (propName == "appStore") { + // Filter appStore based on platform block restrictions + let filteredAppStore = this._filterAppStoreByPlatform(this[propName]); this.querySelector("#hax").setAttribute( "app-store", - JSON.stringify(this[propName]), + JSON.stringify(filteredAppStore), ); } if (propName == "activeItem") { @@ -504,11 +559,9 @@ class HAXCMSSiteEditor extends LitElement { { signal: this.windowControllers.signal }, ); - globalThis.addEventListener( - "json-outline-schema-active-item-changed", - this._newActiveItem.bind(this), - { signal: this.windowControllers.signal }, - ); + // Note: activeItem is now synced via MobX autorun in constructor + // The json-outline-schema-active-item-changed event is still fired by the store + // for backward compatibility with external consumers globalThis.addEventListener( "json-outline-schema-active-body-changed", @@ -552,6 +605,14 @@ class HAXCMSSiteEditor extends LitElement { }, ); + globalThis.addEventListener( + "haxcms-save-node-details", + this.saveNodeDetails.bind(this), + { + signal: this.windowControllers.signal, + }, + ); + globalThis.addEventListener( "haxcms-delete-node", this.deleteNode.bind(this), @@ -623,6 +684,18 @@ class HAXCMSSiteEditor extends LitElement { */ async createNode(e) { + // Check platform configuration before allowing page creation + const platformConfig = + this.manifest && + this.manifest.metadata && + this.manifest.metadata.platform; + if (platformConfig && platformConfig.addPage === false) { + store.toast("Adding pages is disabled for this site", 3000, { + fire: true, + }); + return; + } + if (e.detail.values) { var reqBody = e.detail.values; // ensure site name and jwt are set in request @@ -755,16 +828,13 @@ class HAXCMSSiteEditor extends LitElement { modal: true, styles: { "--simple-modal-titlebar-background": "transparent", - "--simple-modal-titlebar-color": "black", + "--simple-modal-titlebar-color": "light-dark(black, white)", "--simple-modal-width": "90vw", "--simple-modal-min-width": "300px", "--simple-modal-z-index": "100000000", "--simple-modal-height": "90vh", "--simple-modal-min-height": "400px", - "--simple-modal-titlebar-height": "64px", - "--simple-modal-titlebar-color": "black", "--simple-modal-titlebar-height": "80px", - "--simple-modal-titlebar-background": "orange", }, }, }), @@ -815,6 +885,16 @@ class HAXCMSSiteEditor extends LitElement { */ deleteNode(e) { + // Check platform configuration before allowing delete + const platformConfig = + this.manifest && + this.manifest.metadata && + this.manifest.metadata.platform; + if (platformConfig && platformConfig.delete === false) { + store.toast("Delete is disabled for this site", 3000, { fire: true }); + return; + } + this.querySelector("#deleteajax").body = { jwt: this.jwt, site: { @@ -880,13 +960,6 @@ class HAXCMSSiteEditor extends LitElement { this.activeItem.id; } } - /** - * update the internal active item - */ - - _newActiveItem(e) { - this.activeItem = e.detail; - } /** * active item changed */ @@ -939,7 +1012,13 @@ class HAXCMSSiteEditor extends LitElement { // Restore active element position after DOM update for "keep editing" mode if (this._restoreKeepEditMode && this._restoreActiveIndex !== null) { - setTimeout(() => { + // Clean up any existing listener to prevent duplicates + if (this._contentReadyHandler) { + HAXStore.activeHaxBody.removeEventListener('hax-body-content-ready', this._contentReadyHandler); + } + + // Wait for hax-body content-ready event instead of arbitrary timeout + this._contentReadyHandler = () => { try { if (HAXStore.activeHaxBody && HAXStore.activeHaxBody.children) { const bodyChildren = Array.from(HAXStore.activeHaxBody.children); @@ -965,6 +1044,21 @@ class HAXCMSSiteEditor extends LitElement { } } } + + // Force UI component to re-render to update button visibility + // editMode stayed true, so autorun won't fire - need manual update + // Use RAF to ensure DOM is settled before requesting update + requestAnimationFrame(() => { + const uiElement = globalThis.document.querySelector('haxcms-site-editor-ui'); + if (uiElement) { + // Force observable to fire by toggling and restoring + const currentMode = store.editMode; + store.editMode = false; + requestAnimationFrame(() => { + store.editMode = currentMode; + }); + } + }); } catch (error) { console.warn( "Failed to restore active element position after save:", @@ -974,7 +1068,13 @@ class HAXCMSSiteEditor extends LitElement { // Clean up the restoration flags this._restoreActiveIndex = null; this._restoreKeepEditMode = false; - }, 100); // Small delay to ensure DOM is fully updated + this._contentReadyHandler = null; + }; + + // Listen for content-ready event from hax-body + if (HAXStore.activeHaxBody) { + HAXStore.activeHaxBody.addEventListener('hax-body-content-ready', this._contentReadyHandler, { once: true }); + } } // force an update in the store to reprocess what is "active" @@ -1019,6 +1119,20 @@ class HAXCMSSiteEditor extends LitElement { }, 300); } + _handleNodeDetailsResponse(e) { + setTimeout(() => { + store.playSound("coin"); + this.dispatchEvent( + new CustomEvent("haxcms-trigger-update", { + bubbles: true, + composed: true, + cancelable: false, + detail: true, + }), + ); + store.toast(`Operation completed!`, 3000, { hat: "construction" }); + }, 300); + } /** * Save node event */ @@ -1043,7 +1157,12 @@ class HAXCMSSiteEditor extends LitElement { this._restoreKeepEditMode = false; } + // Serialize current DOM content (including page-break) as-is. Entity + // normalization for attributes like title/description is handled on + // the backend so we do not clobber freshly edited values here. let body = await HAXStore.activeHaxBody.haxToContent(); + const schema = await HAXStore.htmlToHaxElements(body); + this.querySelector("#nodeupdateajax").body = { jwt: this.jwt, site: { @@ -1052,7 +1171,7 @@ class HAXCMSSiteEditor extends LitElement { node: { id: this.activeItem.id, body: body, - schema: await HAXStore.htmlToHaxElements(body), + schema: schema, }, }; this.setProcessingVisual(); @@ -1064,9 +1183,21 @@ class HAXCMSSiteEditor extends LitElement { */ saveNodeDetails(e) { + // Check platform configuration before allowing outline operations + const platformConfig = + this.manifest && + this.manifest.metadata && + this.manifest.metadata.platform; + if (platformConfig && platformConfig.outlineDesigner === false) { + store.toast("Outline operations are disabled for this site", 3000, { + fire: true, + }); + return; + } + // send the request - if (this.saveNodePath) { - this.querySelector("#nodeupdateajax").body = { + if (this.saveNodeDetailsPath) { + this.querySelector("#nodedetailsajax").body = { jwt: this.jwt, site: { name: this.manifest.metadata.site.name, @@ -1077,7 +1208,7 @@ class HAXCMSSiteEditor extends LitElement { }, }; this.setProcessingVisual(); - this.querySelector("#nodeupdateajax").generateRequest(); + this.querySelector("#nodedetailsajax").generateRequest(); } } /** diff --git a/elements/haxcms-elements/lib/core/haxcms-site-insights.js b/elements/haxcms-elements/lib/core/haxcms-site-insights.js index 45e503d9a5..5439a291d6 100644 --- a/elements/haxcms-elements/lib/core/haxcms-site-insights.js +++ b/elements/haxcms-elements/lib/core/haxcms-site-insights.js @@ -9,7 +9,8 @@ import "@haxtheweb/accent-card/accent-card.js"; import "@haxtheweb/retro-card/retro-card.js"; import "@haxtheweb/simple-img/simple-img.js"; import "@haxtheweb/simple-fields/simple-fields.js"; -import "@haxtheweb/lesson-overview/lib/lesson-highlight.js"; +import "@haxtheweb/simple-toolbar/lib/simple-toolbar-button.js"; +import "../ui-components/lesson-overview/lib/lesson-highlight.js"; import "@github/time-elements/dist/relative-time-element.js"; import "@haxtheweb/iframe-loader/lib/loading-indicator.js"; import { learningComponentTypes } from "@haxtheweb/d-d-d/lib/DDDStyles.js"; @@ -32,26 +33,74 @@ class HAXCMSShareDialog extends HAXCMSI18NMixin(LitElement) { return [ css` :host { - display: block; - overflow: auto; - height: 85vh; - padding: 16px; + display: flex; + flex-direction: column; + overflow: hidden; } :host([hide-results*="link-status-false"]) .link-status-false, :host([hide-results*="link-status-true"]) .link-status-true { display: none; } .selector-wrapper { - margin-bottom: 32px; - font-size: 24px; - line-height: 24px; + margin-bottom: var(--ddd-spacing-6); + font-size: var(--ddd-font-size-m); + line-height: var(--ddd-lh-120); + display: flex; + align-items: center; + gap: var(--ddd-spacing-3); + flex-wrap: wrap; } - .selector-wrapper select, - .selector-wrapper button { - font-size: 20px; - line-height: 24px; - margin: 0 8px; - padding: 4px; + .selector-wrapper label { + font-weight: var(--ddd-font-weight-bold); + font-family: var(--ddd-font-navigation); + color: light-dark( + var(--ddd-theme-default-coalyGray), + var(--ddd-theme-default-white) + ); + } + .selector-wrapper select { + font-size: var(--ddd-font-size-s); + line-height: var(--ddd-lh-120); + padding: var(--ddd-spacing-2); + border: var(--ddd-border-xs); + border-color: light-dark( + var(--ddd-theme-default-limestoneGray), + var(--ddd-primary-5) + ); + border-radius: var(--ddd-radius-xs); + background-color: light-dark( + var(--ddd-theme-default-white), + var(--ddd-primary-3) + ); + color: light-dark( + var(--ddd-theme-default-coalyGray), + var(--ddd-theme-default-white) + ); + font-family: var(--ddd-font-navigation); + } + .selector-wrapper simple-toolbar-button { + --simple-toolbar-button-border-width: 1px; + --simple-toolbar-border-color: light-dark( + var(--ddd-theme-default-limestoneGray), + var(--ddd-primary-5) + ); + --simple-toolbar-border-radius: var(--ddd-radius-xs); + --simple-toolbar-button-padding: var(--ddd-spacing-2); + background-color: light-dark( + var(--ddd-theme-default-skyBlue), + var(--ddd-primary-4) + ); + color: light-dark( + var(--ddd-theme-default-white), + var(--ddd-theme-default-white) + ); + font-family: var(--ddd-font-navigation); + } + .selector-wrapper simple-toolbar-button:hover { + background-color: light-dark( + var(--ddd-theme-default-original87Pink), + var(--ddd-primary-5) + ); } ul { list-style: none; @@ -59,38 +108,88 @@ class HAXCMSShareDialog extends HAXCMSI18NMixin(LitElement) { padding: 0; } a11y-tabs { - --a11y-tabs-horizontal-background: white; + flex: 1; + display: flex; + flex-direction: column; + --a11y-tabs-horizontal-background: light-dark( + var(--ddd-theme-default-white), + var(--ddd-primary-3) + ); + } + a11y-tabs::part(tabs-container) { + flex-shrink: 0; + } + a11y-tabs::part(content-container) { + flex: 1; + overflow: auto; + min-height: 0; } a11y-tabs::part(tab) { - font-size: 24px; + font-size: var(--ddd-font-size-m); + font-family: var(--ddd-font-navigation); + color: light-dark( + var(--ddd-theme-default-coalyGray), + var(--ddd-theme-default-slateGray) + ); + border-bottom: 3px solid transparent; + transition: all 0.3s ease-in-out; + } + a11y-tabs::part(tab):hover { + color: light-dark( + var(--ddd-theme-default-original87Pink), + var(--ddd-theme-default-skyBlue) + ); } a11y-tabs[active-tab="insights"]::part(tab-insights) { - color: var(--simple-colors-default-theme-purple-8); + color: light-dark( + var(--ddd-theme-default-futureLime), + var(--ddd-theme-default-futureLime) + ); + border-bottom-color: var(--ddd-theme-default-futureLime); } a11y-tabs[active-tab="linkchecker"]::part(tab-linkchecker) { - color: var(--simple-colors-default-theme-orange-8); + color: light-dark( + var(--ddd-theme-default-opportunityGreen), + var(--ddd-theme-default-opportunityGreen) + ); + border-bottom-color: var(--ddd-theme-default-opportunityGreen); } a11y-tabs[active-tab="mediabrowser"]::part(tab-mediabrowser) { - color: var(--simple-colors-default-theme-red-8); + color: light-dark( + var(--ddd-theme-default-original87Pink), + var(--ddd-theme-default-original87Pink) + ); + border-bottom-color: var(--ddd-theme-default-original87Pink); } a11y-tabs[active-tab="contentbrowser"]::part(tab-contentbrowser) { - color: var(--simple-colors-default-theme-blue-8); + color: light-dark( + var(--ddd-theme-default-skyBlue), + var(--ddd-theme-default-skyBlue) + ); + border-bottom-color: var(--ddd-theme-default-skyBlue); + } + a11y-tab { + display: block; + } + .insights, + .tab-content-wrapper { + padding: var(--ddd-spacing-4); + max-height: 100%; + overflow-y: auto; } a11y-tab#insights loading-indicator { - --loading-indicator-color: var( - --simple-colors-default-theme-purple-8 - ); + --loading-indicator-color: var(--ddd-theme-default-futureLime); } a11y-tab#linkchecker loading-indicator { --loading-indicator-color: var( - --simple-colors-default-theme-orange-8 + --ddd-theme-default-opportunityGreen ); } a11y-tab#mediabrowser loading-indicator { - --loading-indicator-color: var(--simple-colors-default-theme-red-8); + --loading-indicator-color: var(--ddd-theme-default-original87Pink); } a11y-tab#contentbrowser loading-indicator { - --loading-indicator-color: var(--simple-colors-default-theme-blue-8); + --loading-indicator-color: var(--ddd-theme-default-skyBlue); } simple-fields-field[type="checkbox"], simple-fields-field[type="select"] { @@ -144,6 +243,7 @@ class HAXCMSShareDialog extends HAXCMSI18NMixin(LitElement) { } .insights { column-count: 3; + padding: var(--ddd-spacing-4); } .insights .recently-updated-items { font-size: 16px; @@ -176,7 +276,11 @@ class HAXCMSShareDialog extends HAXCMSI18NMixin(LitElement) { width: 275px; } .mediabrowser-wrapper { - overflow: hidden; + padding: var(--ddd-spacing-4); + } + .content-list, + .media-list { + padding: var(--ddd-spacing-4); } `, ]; @@ -587,7 +691,6 @@ class HAXCMSShareDialog extends HAXCMSI18NMixin(LitElement) { id="tabs" full-width @a11y-tabs-active-changed="${this.activeChanged}" - sticky > - : ${items.map( (item) => html`
    `; } } diff --git a/elements/haxcms-elements/lib/core/haxcms-site-store.js b/elements/haxcms-elements/lib/core/haxcms-site-store.js index a39eb6a647..6129df7a26 100644 --- a/elements/haxcms-elements/lib/core/haxcms-site-store.js +++ b/elements/haxcms-elements/lib/core/haxcms-site-store.js @@ -303,7 +303,7 @@ class Store { import.meta.url, ).href, ); - this.audio.volume = 0.3; + this.audio.volume = 0.2; this.audio.play(); break; default: @@ -313,7 +313,7 @@ class Store { import.meta.url, ).href, ); - this.audio.volume = 0.3; + this.audio.volume = 0.2; this.audio.play(); console.warn(`${sound} is not a valid sound file yet`); break; @@ -414,10 +414,14 @@ class Store { if (!item.metadata.hasOwnProperty("locked")) { array[index].metadata.locked = false; } - // we default locked to false if not set + // we default status to "" if not set if (!item.metadata.hasOwnProperty("status")) { array[index].metadata.status = ""; } + // normalize parent to null if empty string or undefined + if (array[index].parent === "" || array[index].parent === undefined) { + array[index].parent = null; + } }); var site = new JsonOutlineSchema(); // we already have our items, pass them in @@ -1329,13 +1333,10 @@ class Store { * @returns {string} - The default HTML content for the style guide */ getDefaultStyleGuideContent() { - return ` -

    Heading 1 Example

    -
    - - -

    Heading 2

    -
    `; + // Default style guide content is intentionally empty. + // Themes that want style-guide-driven behavior should + // provide their own theme/style-guide.html file. + return ""; } /** @@ -1528,6 +1529,37 @@ class HAXCMSSiteStore extends HTMLElement { return true; }); if (found) { + // Check if this page has a link URL configured and user is not logged in + // Only perform redirect check if the app and backend are fully ready to avoid timing issues + if ( + store.appReady && + store.cmsSiteEditorBackend.instance && + found.metadata && + found.metadata.linkUrl && + !store.isLoggedIn + ) { + // Additional delay to ensure JWT authentication check is complete + setTimeout(() => { + // Double-check authentication state after backend has had time to initialize + if (!store.isLoggedIn) { + // Handle link redirect for non-authenticated users + const linkTarget = found.metadata.linkTarget || "_self"; + if (linkTarget === "_blank") { + globalThis.open( + found.metadata.linkUrl, + "_blank", + "noopener,noreferrer", + ); + } else { + globalThis.location.href = found.metadata.linkUrl; + } + } else { + // User is authenticated, proceed normally + store.activeId = id; + } + }, 100); + return; // Don't set activeId immediately, wait for timeout + } store.activeId = id; } else if (store.getInternalRoute()) { // we need other stuff to work with this. diff --git a/elements/haxcms-elements/lib/core/haxcms-toast.js b/elements/haxcms-elements/lib/core/haxcms-toast.js index 9fd48b815f..99d88eea4a 100644 --- a/elements/haxcms-elements/lib/core/haxcms-toast.js +++ b/elements/haxcms-elements/lib/core/haxcms-toast.js @@ -1,7 +1,7 @@ import { autorun, toJS } from "mobx"; import { css } from "lit"; import { store } from "./haxcms-site-store.js"; -import { RPGCharacterToast } from "@haxtheweb/app-hax/lib/rpg-character-toast/rpg-character-toast.js"; +import { RPGCharacterToast } from "./ui/rpg-character-toast/rpg-character-toast.js"; export class HAXCMSToast extends RPGCharacterToast { static get tag() { diff --git a/elements/haxcms-elements/lib/core/micros/haxcms-button-add.js b/elements/haxcms-elements/lib/core/micros/haxcms-button-add.js index 7a1877d101..1150ecd98f 100644 --- a/elements/haxcms-elements/lib/core/micros/haxcms-button-add.js +++ b/elements/haxcms-elements/lib/core/micros/haxcms-button-add.js @@ -3,6 +3,7 @@ import { HAXStore } from "@haxtheweb/hax-body/lib/hax-store.js"; import { HAXCMSButton } from "../utils/HAXCMSButton.js"; import { SimpleToolbarButtonBehaviors } from "@haxtheweb/simple-toolbar/lib/simple-toolbar-button.js"; import { toJS } from "mobx"; +import { css } from "lit"; export class HAXCMSButtonAdd extends SimpleToolbarButtonBehaviors( HAXCMSButton, ) { @@ -38,6 +39,9 @@ export class HAXCMSButtonAdd extends SimpleToolbarButtonBehaviors( ...this.simpleButtonCoreStyles, ...this.simpleButtonLayoutStyles, ...this.simpleButtonThemeStyles, + css`:host { + --simple-toolbar-border-radius: 0; + }` ]; } @@ -181,12 +185,9 @@ export class HAXCMSButtonAdd extends SimpleToolbarButtonBehaviors( if (this.autoEdit) { // force hax tray to open HAXStore.haxTray.collapsed = false; - // @todo this implies a timing issue on response and the wiping of material - // see https://github.com/haxtheweb/issues/issues/938 - setTimeout(() => { - // force into edit mode - store.editMode = true; - }, 250); + // hax-body's ContentStateManager now handles timing internally + // no delay needed - edit mode will wait for content stability + store.editMode = true; } } } diff --git a/elements/haxcms-elements/lib/core/micros/haxcms-page-operations.js b/elements/haxcms-elements/lib/core/micros/haxcms-page-operations.js new file mode 100644 index 0000000000..b2c60801b1 --- /dev/null +++ b/elements/haxcms-elements/lib/core/micros/haxcms-page-operations.js @@ -0,0 +1,365 @@ +import { html, css } from "lit"; +import { DDD } from "@haxtheweb/d-d-d/d-d-d.js"; +import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import { store } from "../haxcms-site-store.js"; +import { toJS, autorun } from "mobx"; +import "@haxtheweb/simple-icon/lib/simple-icon-button-lite.js"; +import "@haxtheweb/simple-toolbar/lib/simple-toolbar-button.js"; +import "@haxtheweb/simple-fields/lib/simple-context-menu.js"; + +export class HAXCMSPageOperations extends I18NMixin(DDD) { + static get tag() { + return "haxcms-page-operations"; + } + static get styles() { + return [ + super.styles, + css` + :host { + display: inline-block; + position: relative; + } + simple-icon-button-lite.ops { + --simple-icon-button-border-width: 1px; + --simple-icon-button-border-color: var(--ddd-border-1, #e0e0e0); + --simple-icon-height: var(--haxcms-page-operations-ops-icon-size, var(--ddd-icon-4xs, 14px)); + --simple-icon-width: var(--haxcms-page-operations-ops-icon-size, var(--ddd-icon-4xs, 14px)); + padding: var(--ddd-spacing-1) var(--ddd-spacing-1); + color: var(--ddd-theme-default-white); + background-color: var(--ddd-theme-default-skyBlue); + border: var(--ddd-border-xs); + box-shadow: var(--ddd-boxShadow-sm); + position: relative; + height: var(--haxcms-page-operations-ops-height, 24px); + width: var(--haxcms-page-operations-ops-width, 24px); + } + simple-context-menu { + --simple-context-menu-background: var( + --simple-toolbar-button-bg, + white + ); + --simple-context-menu-background-dark: var( + --simple-toolbar-button-bg, + #000000 + ); + --simple-context-menu-border-color: var( + --simple-toolbar-button-border-color, + var(--simple-toolbar-border-color, #e0e0e0) + ); + --simple-context-menu-border-color-dark: var( + --simple-toolbar-button-border-color, + var(--simple-toolbar-border-color, #444) + ); + --simple-context-menu-shadow: var( + --simple-toolbar-menu-list-box-shadow, + 0 2px 8px rgba(0, 0, 0, 0.15) + ); + --simple-context-menu-min-width: 180px; + } + simple-toolbar-button { + --simple-toolbar-button-border-radius: 0; + --simple-toolbar-button-hover-border-color: transparent; + cursor: pointer; + } + simple-toolbar-button.delete-button { + border-top: var(--ddd-border-sm) solid + var(--ddd-theme-default-limestoneGray); + margin-top: var(--ddd-spacing-2); + padding-top: var(--ddd-spacing-2); + } + simple-toolbar-button.delete-button:focus, + simple-toolbar-button.delete-button:hover { + color: black; + background-color: var(--ddd-theme-default-discoveryCoral); + } + `, + ]; + } + static get properties() { + return { + ...super.properties, + actionId: { type: String, attribute: "action-id" }, + platformAllowsOutline: { type: Boolean }, + platformAllowsDelete: { type: Boolean }, + platformAllowsAddPage: { type: Boolean }, + isLocked: { type: Boolean }, + // Track global edit mode so we can disable page creation while editing + editMode: { type: Boolean }, + }; + } + constructor() { + super(); + this.actionId = null; + this.platformAllowsOutline = true; + this.platformAllowsDelete = true; + this.platformAllowsAddPage = true; + this.isLocked = false; + this.editMode = false; + this.t = { + ...super.t, + outlineActions: "Outline actions", + editPage: "Edit page", + addPage: "Add child page", + moveUp: "Move up", + moveDown: "Move down", + indent: "Indent", + outdent: "Outdent", + moveTo: "Move to..", + siteOutline: "Site Outline", + delete: "Delete", + unlock: "Unlock", + lock: "Lock", + }; + this.registerLocalization({ + context: this, + basePath: import.meta.url, + }); + + // Watch for platform configuration changes + autorun(() => { + const manifest = toJS(store.manifest); + const platformConfig = + manifest && manifest.metadata && manifest.metadata.platform; + + // Check each capability - defaults to true if not specified + this.platformAllowsOutline = + !platformConfig || platformConfig.outlineDesigner !== false; + this.platformAllowsDelete = + !platformConfig || platformConfig.delete !== false; + this.platformAllowsAddPage = + !platformConfig || platformConfig.addPage !== false; + }); + + // Watch for active item lock state + autorun(() => { + const activeItem = toJS(store.activeItem); + this.isLocked = + activeItem && activeItem.metadata && activeItem.metadata.locked + ? true + : false; + }); + + // Keep local editMode in sync with global store state so we can + // hide page creation controls while actively editing content + autorun(() => { + this.editMode = toJS(store.editMode); + }); + } + + _toggleDialog() { + const menu = this.shadowRoot.querySelector("simple-context-menu"); + const button = this.shadowRoot.querySelector(".ops"); + if (menu && button) { + menu.toggle(button); + } + } + + render() { + // Hide entire menu if outline designer is disabled + if (!this.platformAllowsOutline) { + return html``; + } + + return html` + + + ${this.platformAllowsAddPage && !this.editMode + ? html` + + ` + : ""} + this._op("moveUp")} + > + this._op("moveDown")} + > + this._op("indent")} + > + this._op("outdent")} + > + + + ${this.isLocked + ? html` + + ` + : html``} + ${this.platformAllowsDelete + ? html` + + ` + : ""} + + `; + } + _contextItem() { + return (async () => { + let item = {}; + if (this.actionId && this.actionId != "null") { + item = await store.findItemAsObject(this.actionId); + } else if (this.actionId == "null") { + item = toJS(await store.getLastChildItem(null)); + } else { + item = toJS(store.activeItem); + } + return item; + })(); + } + async _op(operation) { + const item = await this._contextItem(); + if (!item || !item.id) { + this._closeDialog(); + return; + } + store.playSound("click"); + const detail = { id: item.id, operation }; + this.dispatchEvent( + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail, + }), + ); + this._closeDialog(); + } + _editPage() { + this._closeDialog(); + store.cmsSiteEditor.haxCmsSiteEditorUIElement._editButtonTap(); + } + + _deletePage() { + this._closeDialog(); + store.cmsSiteEditor.haxCmsSiteEditorUIElement._deleteButtonTap(); + } + + async _movePageProgram() { + this._closeDialog(); + const item = await this._contextItem(); + if (!item || !item.id) { + return; + } + const SuperDaemonInstance = + globalThis.SuperDaemonManager.requestAvailability(); + store.playSound("click"); + // Use waveWand for proper mini-Merlin invocation + SuperDaemonInstance.waveWand([ + "", // no initial search term + "/", // program context + { pageId: item.id }, // pass the page ID to move + "move-page", // program machine name + "Move Page", // program display name + "", // no initial search + ]); + } + + _closeDialog() { + const menu = + this.shadowRoot && this.shadowRoot.querySelector("simple-context-menu"); + if (menu) { + menu.close(); + } + } + + async _toggleLock() { + this._closeDialog(); + const item = await this._contextItem(); + if (!item || !item.id) { + return; + } + store.playSound("click"); + globalThis.dispatchEvent( + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: item.id, + operation: "setLocked", + locked: !this.isLocked, + }, + }), + ); + } + + _openSiteOutline() { + this._closeDialog(); + store.cmsSiteEditor.haxCmsSiteEditorUIElement._outlineButtonTap(); + } +} + +customElements.define(HAXCMSPageOperations.tag, HAXCMSPageOperations); diff --git a/elements/haxcms-elements/lib/core/themes/haxcms-blank-theme.js b/elements/haxcms-elements/lib/core/themes/haxcms-blank-theme.js index f7d3951020..0372f78186 100644 --- a/elements/haxcms-elements/lib/core/themes/haxcms-blank-theme.js +++ b/elements/haxcms-elements/lib/core/themes/haxcms-blank-theme.js @@ -1,6 +1,9 @@ /** * Copyright 2021 The Pennsylvania State University * @license Apache-2.0, see License.md for full text. + * + * @haxcms-theme-category Website + * @haxcms-theme-internal true */ import { html, css } from "lit"; import { CleanTwo } from "@haxtheweb/clean-two/clean-two.js"; diff --git a/elements/haxcms-elements/lib/core/themes/haxcms-json-theme.js b/elements/haxcms-elements/lib/core/themes/haxcms-json-theme.js index 4aabc6d44f..de53c84fb0 100644 --- a/elements/haxcms-elements/lib/core/themes/haxcms-json-theme.js +++ b/elements/haxcms-elements/lib/core/themes/haxcms-json-theme.js @@ -1,6 +1,9 @@ /** * Copyright 2025 The Pennsylvania State University * @license Apache-2.0, see License.md for full text. + * + * @haxcms-theme-category Website + * @haxcms-theme-internal true */ import { html, css } from "lit"; import { HAXCMSPrintTheme } from "./haxcms-print-theme.js"; diff --git a/elements/haxcms-elements/lib/core/themes/haxcms-print-theme.js b/elements/haxcms-elements/lib/core/themes/haxcms-print-theme.js index 4840afd2bd..a0a3c13767 100644 --- a/elements/haxcms-elements/lib/core/themes/haxcms-print-theme.js +++ b/elements/haxcms-elements/lib/core/themes/haxcms-print-theme.js @@ -1,6 +1,9 @@ /** * Copyright 2021 The Pennsylvania State University * @license Apache-2.0, see License.md for full text. + * + * @haxcms-theme-category Website + * @haxcms-theme-internal true */ import { html, css } from "lit"; import { CleanTwo } from "@haxtheweb/clean-two/clean-two.js"; diff --git a/elements/haxcms-elements/lib/core/ui/app-hax-top-bar.js b/elements/haxcms-elements/lib/core/ui/app-hax-top-bar.js new file mode 100644 index 0000000000..ec18343663 --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/app-hax-top-bar.js @@ -0,0 +1,129 @@ +// dependencies / things imported +import { LitElement, html, css } from "lit"; + +// top bar of the UI +export class AppHaxTopBar extends LitElement { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-top-bar"; + } + + constructor() { + super(); + this.editMode = false; + } + + static get properties() { + return { + editMode: { + type: Boolean, + reflect: true, + attribute: "edit-mode", + }, + }; + } + + static get styles() { + return css` + :host { + --bg-color: var(--app-hax-background-color); + --accent-color: var(--app-hax-accent-color); + --top-bar-height: 46px; + display: block; + height: var(--top-bar-height); + } + + /* @media (prefers-color-scheme: dark) { + :root { + --accent-color: white; + color: var(--accent-color); + + } + + :host { + background-color: black; + } + } */ + + .topBar { + overflow: hidden; + background-color: var(--bg-color); + color: var(--accent-color); + height: var(--top-bar-height); + text-align: center; + vertical-align: middle; + border-bottom: 2px solid var(--app-hax-accent-color); + display: grid; + grid-template-columns: 32.5% 35% 32.5%; + transition: border-bottom 0.6s ease-in-out; + } + + /* .topBar > div { + background-color: rgba(255, 255, 255, 0.8); + border: 1px solid black; + } */ + + .topBar .left { + text-align: left; + height: var(--top-bar-height); + vertical-align: text-top; + } + + .topBar .center { + text-align: center; + height: var(--top-bar-height); + vertical-align: text-top; + } + + .topBar .right { + text-align: right; + height: var(--top-bar-height); + vertical-align: text-top; + } + @media (max-width: 640px) { + .topBar .left { + opacity: 0; + pointer-events: none; + } + .topBar .center { + text-align: left; + } + .topBar .right { + text-align: left; + } + #home { + display: none; + } + app-hax-search-bar { + display: none; + } + .topBar { + grid-template-columns: 0% 35% 65%; + display: inline-grid; + } + } + `; + } + + render() { + return html` +
    +
    + +
    +
    + +
    +
    + +
    +
    + `; + } +} +customElements.define(AppHaxTopBar.tag, AppHaxTopBar); diff --git a/elements/haxcms-elements/lib/core/ui/app-hax-user-menu-button.js b/elements/haxcms-elements/lib/core/ui/app-hax-user-menu-button.js new file mode 100644 index 0000000000..301fa174c9 --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/app-hax-user-menu-button.js @@ -0,0 +1,129 @@ +// TODO: Text-overflow-ellipses + +// dependencies / things imported +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; + +export class AppHaxUserMenuButton extends DDDSuper(LitElement) { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-user-menu-button"; + } + + constructor() { + super(); + this.icon = "account-circle"; + this.label = "Default"; + } + + handleClick(e) { + // Find the parent anchor element and trigger its click + const parentAnchor = this.parentElement; + if (parentAnchor && parentAnchor.tagName.toLowerCase() === "a") { + e.stopPropagation(); + parentAnchor.click(); + } + } + + static get properties() { + return { + icon: { type: String }, + label: { type: String }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + font-family: var(--ddd-font-primary, sans-serif); + text-align: center; + width: 100%; + display: block; + } + + .menu-button { + display: flex; + align-items: center; + width: 100%; + border: none; + margin: 0; + padding: var(--ddd-spacing-2, 8px) var(--ddd-spacing-3, 12px); + font-size: var(--ddd-font-size-3xs, 12px); + text-align: left; + color: var(--ddd-theme-default-nittanyNavy, #001e44); + background: transparent; + cursor: pointer; + font-family: var(--ddd-font-primary, sans-serif); + transition: all 0.2s ease; + min-height: var(--ddd-spacing-8, 32px); + box-sizing: border-box; + } + + :host([dark]) .menu-button, + body.dark-mode .menu-button { + color: var(--ddd-theme-default-white, white); + } + + .menu-button:hover, + .menu-button:active, + .menu-button:focus { + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + outline: none; + } + + :host([dark]) .menu-button:hover, + :host([dark]) .menu-button:active, + :host([dark]) .menu-button:focus, + body.dark-mode .menu-button:hover, + body.dark-mode .menu-button:active, + body.dark-mode .menu-button:focus { + background: var(--ddd-theme-default-slateGray, #666); + color: var(--ddd-theme-default-white, white); + } + + :host(.logout) .menu-button:hover, + :host(.logout) .menu-button:active, + :host(.logout) .menu-button:focus { + background: var(--ddd-theme-default-original87Pink, #e4007c); + color: var(--ddd-theme-default-white, white); + } + + .icon { + padding-right: var(--ddd-spacing-2, 8px); + font-size: var(--ddd-font-size-xs, 14px); + flex-shrink: 0; + display: flex; + align-items: center; + } + + .label { + flex: 1; + text-align: left; + } + `, + ]; + } + + render() { + return html` + + `; + } +} +customElements.define(AppHaxUserMenuButton.tag, AppHaxUserMenuButton); diff --git a/elements/haxcms-elements/lib/core/ui/app-hax-user-menu.js b/elements/haxcms-elements/lib/core/ui/app-hax-user-menu.js new file mode 100644 index 0000000000..f2f8b3e6d5 --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/app-hax-user-menu.js @@ -0,0 +1,338 @@ +// TODO: Create app-hax-user-menu-button to be tossed into this +// TODO: Create prefix and suffix sections for sound/light toggles and other shtuff + +// dependencies / things imported +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; + +export class AppHaxUserMenu extends DDDSuper(LitElement) { + // a convention I enjoy so you can change the tag name in 1 place + static get tag() { + return "app-hax-user-menu"; + } + + constructor() { + super(); + this.isOpen = false; + this.icon = "account-circle"; + this.addEventListener("keydown", this._handleKeydown.bind(this)); + } + + static get properties() { + return { + isOpen: { type: Boolean, reflect: true, attribute: "is-open" }, + icon: { type: String, reflect: true }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + font-family: var(--ddd-font-primary, sans-serif); + text-align: center; + display: inline-block; + margin: 0px; + padding: 0px; + position: relative; + } + + .entireComponent { + max-height: var(--ddd-spacing-10, 40px); + } + + .menuToggle { + cursor: pointer; + max-height: var(--ddd-spacing-10, 40px); + } + + .user-menu { + display: none; + } + + .user-menu.open { + display: block; + top: var(--ddd-spacing-12, 48px); + right: 0px; + position: absolute; + border: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-slateGray, #666); + background: var(--ddd-theme-default-white, white); + border-radius: var(--ddd-radius-sm, 4px); + box-shadow: var(--ddd-boxShadow-lg); + min-width: var(--ddd-spacing-30, 200px); + z-index: 1000; + overflow: hidden; + } + + :host([dark]) .user-menu.open, + body.dark-mode .user-menu.open { + background: var(--ddd-theme-default-coalyGray, #333); + border-color: var(--ddd-theme-default-slateGray, #666); + } + + .user-menu.open ::slotted(*) { + display: block; + width: 100%; + margin: 0; + font-size: var(--ddd-font-size-3xs, 12px); + text-align: left; + font-family: var(--ddd-font-primary, sans-serif); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + background: transparent; + text-decoration: none; + } + + :host([dark]) .user-menu.open ::slotted(*), + body.dark-mode .user-menu.open ::slotted(*) { + color: var(--ddd-theme-default-white, white); + } + + .user-menu.open .main-menu ::slotted(*:hover), + .user-menu.open .main-menu ::slotted(*:active), + .user-menu.open .main-menu ::slotted(*:focus) { + background: var(--ddd-theme-default-limestoneGray, #f5f5f5); + color: var(--ddd-theme-default-nittanyNavy, #001e44); + } + + :host([dark]) .user-menu.open .main-menu ::slotted(*:hover), + :host([dark]) .user-menu.open .main-menu ::slotted(*:active), + :host([dark]) .user-menu.open .main-menu ::slotted(*:focus), + body.dark-mode .user-menu.open .main-menu ::slotted(*:hover), + body.dark-mode .user-menu.open .main-menu ::slotted(*:active), + body.dark-mode .user-menu.open .main-menu ::slotted(*:focus) { + background: var(--ddd-theme-default-slateGray, #666); + color: var(--ddd-theme-default-white, white); + } + + .user-menu.open .post-menu ::slotted(*:hover), + .user-menu.open .post-menu ::slotted(*:active), + .user-menu.open .post-menu ::slotted(*:focus) { + background: var(--ddd-theme-default-original87Pink, #e4007c); + color: var(--ddd-theme-default-white, white); + } + + .user-menu ::slotted(button) { + cursor: pointer; + } + + .user-menu ::slotted(*) simple-icon-lite { + padding-right: var(--ddd-spacing-2, 8px); + } + + .pre-menu, + .post-menu { + border-top: var(--ddd-border-xs, 1px solid) + var(--ddd-theme-default-limestoneGray, #f5f5f5); + } + + .pre-menu:first-child, + .main-menu:first-child { + border-top: none; + } + + /* Keyboard focus indicators */ + .user-menu ::slotted(*:focus), + .user-menu ::slotted(*[tabindex="0"]:focus) { + outline: var(--ddd-border-sm, 2px solid) + var(--ddd-theme-default-keystoneYellow, #ffd100); + outline-offset: -2px; + } + `, + ]; + } + + render() { + return html` +
    +
    + +
    + +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + `; + } + + /** + * Handle keyboard navigation for menu toggle + */ + _handleMenuToggleKeydown(e) { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault(); + this._toggleMenu(); + } else if (e.key === "Escape" && this.isOpen) { + e.preventDefault(); + this._closeMenu(); + } + } + + /** + * Handle keyboard navigation within menu + */ + _handleKeydown(e) { + if (!this.isOpen) return; + + const menuItems = this._getMenuItems(); + const currentIndex = this._getCurrentMenuItemIndex(menuItems); + + switch (e.key) { + case "Escape": + e.preventDefault(); + this._closeMenu(); + this._focusToggle(); + break; + case "ArrowDown": + e.preventDefault(); + this._focusNextItem(menuItems, currentIndex); + break; + case "ArrowUp": + e.preventDefault(); + this._focusPreviousItem(menuItems, currentIndex); + break; + case "Home": + e.preventDefault(); + this._focusFirstItem(menuItems); + break; + case "End": + e.preventDefault(); + this._focusLastItem(menuItems); + break; + } + } + + /** + * Toggle menu open/closed state + */ + _toggleMenu() { + this.isOpen = !this.isOpen; + if (this.isOpen) { + // Focus first menu item when opening + setTimeout(() => { + const menuItems = this._getMenuItems(); + if (menuItems.length > 0) { + menuItems[0].focus(); + } + }, 0); + } + } + + /** + * Close menu and restore focus + */ + _closeMenu() { + this.isOpen = false; + } + + /** + * Focus the menu toggle button + */ + _focusToggle() { + const toggle = this.shadowRoot.querySelector(".menuToggle"); + if (toggle) { + toggle.focus(); + } + } + + /** + * Get all focusable menu items + */ + _getMenuItems() { + const menu = this.shadowRoot.querySelector(".user-menu"); + if (!menu) return []; + + const items = menu.querySelectorAll("slot"); + const menuItems = []; + + items.forEach((slot) => { + const assignedElements = slot.assignedElements(); + assignedElements.forEach((el) => { + // Find focusable elements within slotted content + const focusable = el.matches('a, button, [tabindex="0"]') + ? [el] + : el.querySelectorAll('a, button, [tabindex="0"]'); + menuItems.push(...focusable); + }); + }); + + return menuItems; + } + + /** + * Get current focused menu item index + */ + _getCurrentMenuItemIndex(menuItems) { + const activeElement = + this.shadowRoot.activeElement || document.activeElement; + return menuItems.indexOf(activeElement); + } + + /** + * Focus next menu item + */ + _focusNextItem(menuItems, currentIndex) { + const nextIndex = + currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0; + if (menuItems[nextIndex]) { + menuItems[nextIndex].focus(); + } + } + + /** + * Focus previous menu item + */ + _focusPreviousItem(menuItems, currentIndex) { + const prevIndex = + currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1; + if (menuItems[prevIndex]) { + menuItems[prevIndex].focus(); + } + } + + /** + * Focus first menu item + */ + _focusFirstItem(menuItems) { + if (menuItems[0]) { + menuItems[0].focus(); + } + } + + /** + * Focus last menu item + */ + _focusLastItem(menuItems) { + if (menuItems[menuItems.length - 1]) { + menuItems[menuItems.length - 1].focus(); + } + } +} +customElements.define(AppHaxUserMenu.tag, AppHaxUserMenu); diff --git a/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleL.svg b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleL.svg new file mode 100644 index 0000000000..4ba1960b1f --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleL.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleMiddle.svg b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleMiddle.svg new file mode 100644 index 0000000000..85b761ae70 --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleMiddle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleR.svg b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleR.svg new file mode 100644 index 0000000000..af0271dd8d --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/images/SpeechBubbleR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/elements/haxcms-elements/lib/core/ui/rpg-character-toast/rpg-character-toast.js b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/rpg-character-toast.js new file mode 100644 index 0000000000..b399d5c6a2 --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/rpg-character-toast/rpg-character-toast.js @@ -0,0 +1,432 @@ +import { css, html, unsafeCSS } from "lit"; +import { SimpleToastEl } from "@haxtheweb/simple-toast/lib/simple-toast-el.js"; +import "@haxtheweb/rpg-character/rpg-character.js"; +import "@haxtheweb/future-terminal-text/future-terminal-text.js"; + +const SpeechBubbleL = new URL("./images/SpeechBubbleL.svg", import.meta.url) + .href; +const SpeechBubbleMiddle = new URL( + "./images/SpeechBubbleMiddle.svg", + import.meta.url, +).href; +const SpeechBubbleR = new URL("./images/SpeechBubbleR.svg", import.meta.url) + .href; +export class RPGCharacterToast extends SimpleToastEl { + static get tag() { + return "rpg-character-toast"; + } + + constructor() { + super(); + this.awaitingMerlinInput = false; + this.windowControllers = new AbortController(); + this.text = "Saved"; + this.closeText = "Close"; + this.merlin = false; + this.classStyle = ""; + this.future = false; + this.duration = 3000; + this.accentColor = "grey"; + this.dark = false; + this.eventCallback = null; + this.fire = false; + this.hat = "coffee"; + this.speed = 500; + this.walking = false; + } + + static get styles() { + return [ + super.styles, + css` + :host([opened]) { + display: block; + padding: 0; + margin: 0; + } + + future-terminal-text { + min-width: 300px; + overflow-wrap: break-all; + text-overflow: ellipsis; + line-height: 36px; + font-size: 18px; + text-align: left; + padding: 36px 0px; + max-width: 50vw; + } + + .merlin { + --simple-icon-height: 100px; + --simple-icon-width: 100px; + background-color: var(--simple-colors-default-theme-accent-1, white); + display: block; + height: 150px; + width: 100px; + margin: 6px 0 0 0; + padding: 16px; + } + .awaiting-input { + --simple-icon-height: 50px; + --simple-icon-width: 50px; + width: 100px; + margin: 6px 0px 0px; + padding: 16px; + color: var(--ddd-theme-default-wonderPurple); + vertical-align: middle; + display: inline-flex; + height: 100px; + } + :host([hidden]) { + display: none; + } + :host { + --simple-toast-bottom: 0px; + --simple-toast-bottom-offscreen: -284px; + height: var(--rpg-character-toast-height, 142px); + display: none; + width: var(--simple-toast-width, auto); + min-width: var(--simple-toast-min-width, 200px); + color: var( + --simple-toast-color, + var(--simple-colors-default-theme-accent-12, black) + ); + background-color: var( + --simple-toast-bg, + var(--simple-colors-default-theme-accent-1, white) + ); + top: var(--simple-toast-top); + bottom: var(--simple-toast-bottom, 36px); + right: var(--simple-toast-right, 0px); + border: var(--simple-toast-border); + z-index: var(--simple-toast-z-index, 10000000); + font-size: var(--simple-toast-font-size, 18px); + font-family: sans-serif; + font-weight: bold; + text-align: center; + vertical-align: middle; + } + rpg-character { + width: 64px; + margin: 0; + padding: 0; + display: var(--rpg-character-toast-display); + } + .bubble-wrapper { + min-width: var(--simple-toast-min-width, 200px); + display: block; + } + .bubble { + height: var(--rpg-character-toast-height, 142px); + display: inline-flex; + } + .mid { + min-width: 100px; + line-height: var(--rpg-character-toast-height, 142px); + background-repeat: repeat-x; + background-image: var( + --rpg-character-toast-mid-background-image, + url("${unsafeCSS(SpeechBubbleMiddle)}") + ); + padding: var(--rpg-character-toast-mid-padding, 54px 2px 0 2px); + display: block; + } + .message { + line-height: 16px; + font-size: 14px; + height: 16px; + display: block; + margin-top: 16px; + margin-bottom: 16px; + } + .buttons { + display: block; + line-height: 16px; + font-size: 16px; + height: 16px; + } + .dismiss { + padding: 4px; + font-weight: bold; + background-color: black; + color: white; + border: 4px solid black; + border-radius: none; + margin-left: 4px; + cursor: pointer; + } + .leftedge { + background-image: var( + --rpg-character-toast-left-background-image, + url("${unsafeCSS(SpeechBubbleL)}") + ); + width: 20px; + } + .rightedge { + background-image: var( + --rpg-character-toast-right-background-image, + url("${unsafeCSS(SpeechBubbleR)}") + ); + width: 40px; + } + .progress { + border: 2px solid var(--ddd-theme-default-keystoneYellow); + height: 4px; + margin: -6px 0px 0px 0px; + } + + .progress .progress__bar { + height: 100%; + width: 0%; + background-color: var(--ddd-theme-default-keystoneYellow); + animation-delay: 0.3s; + animation-name: fill-bar; + animation-duration: 3s; + animation-iteration-count: 1; + animation-fill-mode: forwards; + } + + @keyframes fill-bar { + from { + width: 0%; + } + to { + width: 100%; + } + } + `, + ]; + } + + static get properties() { + return { + ...super.properties, + darkMode: { + type: Boolean, + reflect: true, + attribute: "dark-mode", + }, + fire: { type: Boolean }, + hat: { type: String }, + walking: { type: Boolean }, + speed: { type: Number }, + /** + * Opened state of the toast, use event to change + */ + opened: { + type: Boolean, + reflect: true, + }, + awaitingMerlinInput: { + type: Boolean, + attribute: "awaiting-merlin-input", + }, + merlin: { + type: Boolean, + }, + future: { + type: Boolean, + }, + /** + * Plain text based message to display + */ + text: { + type: String, + }, + /** + * Class name, fit-bottom being a useful one + */ + classStyle: { + type: String, + attribute: "class-style", + }, + /** + * How long the toast message should be displayed + */ + duration: { + type: Number, + }, + /** + * Event callback when hide is called + */ + eventCallback: { + type: String, + attribute: "event-callback", + }, + }; + } + + render() { + return html` +
    + + + + ${this.future + ? html` ` + : html`
    ${this.text}
    `} + ${this.awaitingMerlinInput + ? html`` + : ``} +
    + + ${this.merlin + ? html` ` + : html` + + `} + ${!this.merlin + ? html`
    + +
    ` + : ``} +
    +
    +
    +
    + `; + } + + updated(changedProperties) { + super.updated(changedProperties); + // can't write here in template bc it's a vanilla innerHTML which would have Lit + // directives in it and we don't want to ingest and rewrite those + if ( + changedProperties.has("text") && + this.future && + this.shadowRoot.querySelector("future-terminal-text") + ) { + this.shadowRoot.querySelector("future-terminal-text").innerText = + this.text; + this.shadowRoot.querySelector("future-terminal-text")._doGlitch(); + } + } + + connectedCallback() { + super.connectedCallback(); + globalThis.addEventListener( + "rpg-character-toast-hide", + this.hideSimpleToast.bind(this), + { signal: this.windowControllers.signal }, + ); + globalThis.addEventListener( + "rpg-character-toast-show", + this.showSimpleToast.bind(this), + { signal: this.windowControllers.signal }, + ); + } + + hideSimpleToast(e) { + // event always trumps waiting for input + this.awaitingMerlinInput = false; + this.hide(); + } + + /** + * life cycle, element is removed from the DOM + */ + disconnectedCallback() { + this.windowControllers.abort(); + super.disconnectedCallback(); + } + + /** + * Show / available callback + */ + showSimpleToast(e) { + // wipe slot + while (this.firstChild !== null) { + this.removeChild(this.firstChild); + } + setTimeout(() => { + if (e.detail.slot) { + this.appendChild(e.detail.slot); + } + }, 0); + // force this element to be hidden prior to showing it + this.duration = e.detail.duration ? e.detail.duration : 4000; + this.fire = e.detail.fire ? e.detail.fire : false; + this.hat = e.detail.hat ? e.detail.hat : "coffee"; + this.merlin = e.detail.merlin ? e.detail.merlin : false; + this.walking = e.detail.walking ? e.detail.walking : false; + this.speed = e.detail.speed ? e.detail.speed : 500; + this.text = e.detail.text ? e.detail.text : "Saved"; + this.future = e.detail.future ? e.detail.future : false; + this.classStyle = e.detail.classStyle ? e.detail.classStyle : ""; + this.eventCallback = e.detail.eventCallback ? e.detail.eventCallback : null; + this.dark = e.detail.dark ? e.detail.dark : false; + this.accentColor = e.detail.accentColor ? e.detail.accentColor : "grey"; + this.alwaysvisible = e.detail.alwaysvisible + ? e.detail.alwaysvisible + : false; + // already open and waiting for input, don't do anything + if (e.detail.awaitingMerlinInput && this.duration) { + // should appear to switch into waiting for input mode prior to closing state + setTimeout(() => { + this.style.animation = "none"; + this.awaitingMerlinInput = e.detail.awaitingMerlinInput; + }, this.duration / 2); + } else { + this.awaitingMerlinInput = false; + } + this.show(); + } + + show() { + if (!this.opened) { + this.opened = true; + } + } + + hide() { + if (!this.awaitingMerlinInput) { + // to avoid constantly running in the background + this.walking = false; + this.speed = 500; + if (this.eventCallback) { + const evt = new CustomEvent(this.eventCallback, { + bubbles: true, + cancelable: true, + detail: true, + composed: true, + }); + this.dispatchEvent(evt); + } + if (!this.alwaysvisible) { + this.opened = false; + } else { + this.style.animation = "fadein 0.3s"; + } + } else { + this.style.animation = "fadein 0.3s"; + } + } +} +globalThis.customElements.define(RPGCharacterToast.tag, RPGCharacterToast); diff --git a/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/moon.svg b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/moon.svg new file mode 100644 index 0000000000..9d6c8ab1cb --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/moonIcon.png b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/moonIcon.png new file mode 100644 index 0000000000..88eb079435 Binary files /dev/null and b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/moonIcon.png differ diff --git a/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/sun.svg b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/sun.svg new file mode 100644 index 0000000000..a555b62ed4 --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/sunIcon.png b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/sunIcon.png new file mode 100644 index 0000000000..6b94b10568 Binary files /dev/null and b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/images/sunIcon.png differ diff --git a/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/wired-darkmode-toggle.js b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/wired-darkmode-toggle.js new file mode 100644 index 0000000000..d7aa691ce3 --- /dev/null +++ b/elements/haxcms-elements/lib/core/ui/wired-darkmode-toggle/wired-darkmode-toggle.js @@ -0,0 +1,158 @@ +import { + rectangle, + hachureEllipseFill, + ellipse, + svgNode, +} from "wired-elements/lib/wired-lib.js"; +import { WiredToggle } from "wired-elements/lib/wired-toggle.js"; +import { html, css, unsafeCSS } from "lit"; +// need to highjack in order to alter the scale so we can fit our icon +// for states +const sun = new URL("./images/sunIcon.png", import.meta.url).href; +const moon = new URL("./images/moonIcon.png", import.meta.url).href; + +export class WiredDarkmodeToggle extends WiredToggle { + constructor() { + super(); + this.checked = false; + this.label = "Dark mode"; + this.knobFill = svgNode("circle"); + } + + // eslint-disable-next-line class-methods-use-this + canvasSize() { + return [100, 48]; + } + + static get tag() { + return "wired-darkmode-toggle"; + } + + draw(svg, size) { + //const rect = rectangle(svg, 0, 0, size[0], 48, this.seed); + //rect.classList.add("toggle-bar"); + this.knob = svgNode("g"); + this.knob.classList.add("knob"); + svg.appendChild(this.knob); + + this.knobFill.setAttribute("cx", 26); + this.knobFill.setAttribute("cy", 26); + this.knobFill.setAttribute("r", 20); + this.knobFill.setAttribute( + "style", + "fill: var(--wired-toggle-off-color); transition: fill 0.3s ease-in-out;", + ); + this.knobFill.classList.add("knobfill"); + this.knob.appendChild(this.knobFill); + ellipse(this.knob, 26, 26, 40, 40, this.seed); + } + + toggleMode(checked) { + if (checked) { + this.knobFill.setAttribute( + "style", + "fill: var(--wired-toggle-on-color);", + ); + } else { + this.knobFill.setAttribute( + "style", + "fill: var(--wired-toggle-off-color);", + ); + } + } + + onChange(event) { + this.checked = event.target.checked; + this.toggleMode(this.checked); + this.dispatchEvent(new Event("change", { bubbles: true, composed: true })); + } + + static get properties() { + return { + checked: { + type: Boolean, + reflect: true, + }, + disabled: { + type: Boolean, + reflect: true, + }, + label: { + type: String, + }, + }; + } + + render() { + return html` +
    + + + +
    + `; + } + + static get styles() { + return [ + super.styles, + css` + :host { + opacity: 1; + display: inline-flex; + vertical-align: top; + } + :host div { + background-image: url("${unsafeCSS(sun)}"); + background-repeat: no-repeat; + --wired-toggle-off-color: var(--simple-colors-fixed-theme-amber-7); + --wired-toggle-on-color: var( + --simple-colors-fixed-theme-light-blue-9 + ); + background-position-x: 60px; + background-position-y: 8px; + width: 100px; + display: inline-flex; + } + :host([checked]) div { + background-image: url("${unsafeCSS(moon)}"); + background-position-x: 12px; + background-position-y: 12px; + background-size: 22px, 22px; + } + :host([disabled]) { + opacity: 0.5; + pointer-events: none; + cursor: not-allowed; + } + input { + width: 100px; + height: 48px; + padding: 0; + margin: 0; + } + label { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; + } + /*img { + top: 8px; + max-width: 20 + height: 35; + }*/ + `, + ]; + } +} +globalThis.customElements.define(WiredDarkmodeToggle.tag, WiredDarkmodeToggle); diff --git a/elements/haxcms-elements/lib/core/utils/EditDescriptionProgram.js b/elements/haxcms-elements/lib/core/utils/EditDescriptionProgram.js new file mode 100644 index 0000000000..21f1d7629f --- /dev/null +++ b/elements/haxcms-elements/lib/core/utils/EditDescriptionProgram.js @@ -0,0 +1,61 @@ +/** + * Edit Description Program for Merlin - Provides description editing capability for HAXcms pages + * This program allows editing the description on the active page + */ +import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; +import { toJS } from "mobx"; + +/** + * Creates the edit description program for use in SuperDaemon + * @param {Object} context - The context object (typically the haxcms-site-editor-ui instance) + * @returns {Function} The program function that returns description editing options + */ +export const createEditDescriptionProgram = (context) => { + return async (input) => { + const results = []; + const activeItem = toJS(store.activeItem); + + if (!activeItem || !activeItem.id) { + return [ + { + title: "No active page found", + icon: "icons:warning", + tags: ["error"], + value: { disabled: true }, + eventName: "disabled", + path: "CMS/edit/description/error", + }, + ]; + } + + // Save button first - most common action + results.push({ + title: + input && input.trim() !== "" + ? `Save description: ${input.substring(0, 50)}${input.length > 50 ? "..." : ""}` + : "Remove description", + icon: "icons:check", + tags: ["confirm", "save"], + value: { + target: globalThis, + method: "dispatchEvent", + args: [ + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: activeItem.id, + operation: "setDescription", + description: input || "", + }, + }), + ], + }, + eventName: "super-daemon-element-method", + path: "CMS/edit/description/confirm", + }); + + return results; + }; +}; diff --git a/elements/haxcms-elements/lib/core/utils/EditSlugProgram.js b/elements/haxcms-elements/lib/core/utils/EditSlugProgram.js new file mode 100644 index 0000000000..015b666c7a --- /dev/null +++ b/elements/haxcms-elements/lib/core/utils/EditSlugProgram.js @@ -0,0 +1,60 @@ +/** + * Edit Slug Program for Merlin - Provides slug editing capability for HAXcms pages + * This program allows editing the URL slug on the active page + */ +import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; +import { toJS } from "mobx"; + +/** + * Creates the edit slug program for use in SuperDaemon + * @param {Object} context - The context object (typically the haxcms-site-editor-ui instance) + * @returns {Function} The program function that returns slug editing options + */ +export const createEditSlugProgram = (context) => { + return async (input) => { + const results = []; + const activeItem = toJS(store.activeItem); + + if (!activeItem || !activeItem.id) { + return [ + { + title: "No active page found", + icon: "icons:warning", + tags: ["error"], + value: { disabled: true }, + eventName: "disabled", + path: "CMS/edit/slug/error", + }, + ]; + } + + // Save button first - most common action + if (input && input.trim() !== "") { + results.push({ + title: `Save slug: ${input}`, + icon: "icons:check", + tags: ["confirm", "save"], + value: { + target: globalThis, + method: "dispatchEvent", + args: [ + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: activeItem.id, + operation: "setSlug", + slug: input, + }, + }), + ], + }, + eventName: "super-daemon-element-method", + path: "CMS/edit/slug/confirm", + }); + } + + return results; + }; +}; diff --git a/elements/haxcms-elements/lib/core/utils/EditTagsProgram.js b/elements/haxcms-elements/lib/core/utils/EditTagsProgram.js new file mode 100644 index 0000000000..1b3615e1c9 --- /dev/null +++ b/elements/haxcms-elements/lib/core/utils/EditTagsProgram.js @@ -0,0 +1,61 @@ +/** + * Edit Tags Program for Merlin - Provides tag editing capability for HAXcms pages + * This program allows editing tags on the active page + */ +import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; +import { toJS } from "mobx"; + +/** + * Creates the edit tags program for use in SuperDaemon + * @param {Object} context - The context object (typically the haxcms-site-editor-ui instance) + * @returns {Function} The program function that returns tag editing options + */ +export const createEditTagsProgram = (context) => { + return async (input) => { + const results = []; + const activeItem = toJS(store.activeItem); + + if (!activeItem || !activeItem.id) { + return [ + { + title: "No active page found", + icon: "icons:warning", + tags: ["error"], + value: { disabled: true }, + eventName: "disabled", + path: "CMS/edit/tags/error", + }, + ]; + } + + // Save button first - most common action + results.push({ + title: + input && input.trim() !== "" + ? `Save tags: ${input}` + : "Remove all tags", + icon: "icons:check", + tags: ["confirm", "save"], + value: { + target: globalThis, + method: "dispatchEvent", + args: [ + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: activeItem.id, + operation: "setTags", + tags: input || "", + }, + }), + ], + }, + eventName: "super-daemon-element-method", + path: "CMS/edit/tags/confirm", + }); + + return results; + }; +}; diff --git a/elements/haxcms-elements/lib/core/utils/EditTitleProgram.js b/elements/haxcms-elements/lib/core/utils/EditTitleProgram.js new file mode 100644 index 0000000000..7b05aa928e --- /dev/null +++ b/elements/haxcms-elements/lib/core/utils/EditTitleProgram.js @@ -0,0 +1,90 @@ +/** + * Edit Title Program for Merlin - Provides title editing capability for HAXcms pages + * This program allows editing the title on the active page + */ +import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; +import { toJS } from "mobx"; + +/** + * Creates the edit title program for use in SuperDaemon + * @param {Object} context - The context object (typically the haxcms-site-editor-ui instance) + * @returns {Function} The program function that returns title editing options + */ +export const createEditTitleProgram = (context) => { + return async (input, values) => { + const results = []; + const activeItem = toJS(store.activeItem); + + if (!activeItem || !activeItem.id) { + return [ + { + title: "No active page found", + icon: "icons:warning", + tags: ["error"], + value: { disabled: true }, + eventName: "disabled", + path: "CMS/edit/title/error", + }, + ]; + } + + const currentTitle = activeItem.title || ""; + + // If no input provided, show option with current title + if (!input || input.trim() === "") { + if (currentTitle) { + results.push({ + title: `Save title: ${currentTitle}`, + icon: "icons:check", + tags: ["confirm", "save"], + value: { + target: globalThis, + method: "dispatchEvent", + args: [ + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: activeItem.id, + operation: "setTitle", + title: currentTitle, + }, + }), + ], + }, + eventName: "super-daemon-element-method", + path: "CMS/edit/title/confirm", + }); + } + return results; + } + + // User has typed something - show save option with their input + results.push({ + title: `Save title: ${input}`, + icon: "icons:check", + tags: ["confirm", "save"], + value: { + target: globalThis, + method: "dispatchEvent", + args: [ + new CustomEvent("haxcms-save-node-details", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + id: activeItem.id, + operation: "setTitle", + title: input, + }, + }), + ], + }, + eventName: "super-daemon-element-method", + path: "CMS/edit/title/confirm", + }); + + return results; + }; +}; diff --git a/elements/haxcms-elements/lib/core/utils/ExportPageProgram.js b/elements/haxcms-elements/lib/core/utils/ExportPageProgram.js index dfa3f22e89..ed45fcb9af 100644 --- a/elements/haxcms-elements/lib/core/utils/ExportPageProgram.js +++ b/elements/haxcms-elements/lib/core/utils/ExportPageProgram.js @@ -193,9 +193,9 @@ export async function _exportPageAsDOCX(content, title) { export async function _exportPageAsPDF(content, title) { try { // Get base URL for PDF generation + const baseElement = globalThis.document.querySelector("base"); const baseUrl = - globalThis.document.querySelector("base")?.href || - globalThis.location.origin; + (baseElement && baseElement.href) || globalThis.location.origin; const response = await MicroFrontendRegistry.call("@core/htmlToPdf", { base: baseUrl, diff --git a/elements/haxcms-elements/lib/core/utils/ExportSiteProgram.js b/elements/haxcms-elements/lib/core/utils/ExportSiteProgram.js index 83825a523d..430ce9fcf8 100644 --- a/elements/haxcms-elements/lib/core/utils/ExportSiteProgram.js +++ b/elements/haxcms-elements/lib/core/utils/ExportSiteProgram.js @@ -56,6 +56,13 @@ export function createExportSiteProgram(context) { format: "zip", description: "Download complete site as ZIP file", }, + { + title: "Export site skeleton", + icon: "icons:description", + format: "skeleton", + description: + "Export as reusable site template for creating similar sites", + }, ]; // Filter results based on input @@ -100,9 +107,9 @@ export async function exportSiteAs(format) { } const siteTitle = manifest.title || "site"; + const baseElement = globalThis.document.querySelector("base"); const baseUrl = - globalThis.document.querySelector("base")?.href || - globalThis.location.origin; + (baseElement && baseElement.href) || globalThis.location.origin; switch (format) { case "html": @@ -129,6 +136,10 @@ export async function exportSiteAs(format) { await this._downloadSiteArchive(); break; + case "skeleton": + await this._exportSiteAsSkeleton(manifest, siteTitle, baseUrl); + break; + default: HAXStore.toast( `Export format "${format}" not supported`, @@ -408,3 +419,44 @@ export function _downloadBlob(blob, filename) { globalThis.document.body.removeChild(link); globalThis.URL.revokeObjectURL(link.href); } + +// Export site as skeleton template +export async function _exportSiteAsSkeleton(manifest, title, baseUrl) { + try { + HAXStore.toast("Generating site skeleton...", 3000, "fit-bottom"); + + // Dynamically import the skeleton generator + const { SiteSkeletonGenerator } = await import( + "./site-skeleton-generator.js" + ); + + // Generate the skeleton + const skeleton = await SiteSkeletonGenerator.generateFromCurrentSite(); + + // Download the skeleton file + const content = JSON.stringify(skeleton, null, 2); + const filename = `${skeleton.meta.name.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-skeleton.json`; + this._downloadFile(content, filename, "application/json"); + + HAXStore.toast( + `Site skeleton exported successfully! Pages: ${skeleton.structure.length}, Files: ${skeleton.files.length}`, + 5000, + "fit-bottom", + ); + + // Log useful information about the skeleton + console.log("Skeleton generated:", { + name: skeleton.meta.name, + pages: skeleton.structure.length, + files: skeleton.files.length, + theme: skeleton.theme.element, + }); + } catch (error) { + console.error("Skeleton export failed:", error); + HAXStore.toast( + `Skeleton export failed: ${error.message}`, + 5000, + "fit-bottom", + ); + } +} diff --git a/elements/haxcms-elements/lib/core/utils/HAXCMSButton.js b/elements/haxcms-elements/lib/core/utils/HAXCMSButton.js index 75b096cd0c..fbb15bafc0 100644 --- a/elements/haxcms-elements/lib/core/utils/HAXCMSButton.js +++ b/elements/haxcms-elements/lib/core/utils/HAXCMSButton.js @@ -5,14 +5,6 @@ import "@haxtheweb/hax-iconset/lib/simple-hax-iconset.js"; import "@haxtheweb/simple-tooltip/simple-tooltip.js"; import { HAXCMSI18NMixin } from "./HAXCMSI18NMixin.js"; import { HAXCMSThemeParts } from "./HAXCMSThemeParts.js"; -const ButtonBGLight = new URL( - "../../../../app-hax/lib/assets/images/ButtonBGLM.svg", - import.meta.url, -).href; -const ButtonBGDark = new URL( - "../../../../app-hax/lib/assets/images/ButtonBGDM.svg", - import.meta.url, -).href; // translation support baked in, use this class to reduce // complexity in adding new buttons to the HAXcms UI for editors @@ -56,7 +48,7 @@ export class HAXCMSButton extends HAXCMSThemeParts( renderButton(label, tooltip) { return html` true, + description = "", + context = "global", + } = options; + + const shortcutKey = this._generateKey(key, ctrl, shift, alt, meta); + + this.shortcuts.set(shortcutKey, { + key, + ctrl, + shift, + alt, + meta, + callback, + condition, + description, + context, + }); + } + + /** + * Unregister a keyboard shortcut + */ + unregister(key, ctrl = false, shift = false, alt = false, meta = false) { + const shortcutKey = this._generateKey(key, ctrl, shift, alt, meta); + this.shortcuts.delete(shortcutKey); + } + + /** + * Generate a unique key for the shortcut map + */ + _generateKey(key, ctrl, shift, alt, meta) { + const parts = []; + if (ctrl) parts.push("Ctrl"); + if (alt) parts.push("Alt"); + if (shift) parts.push("Shift"); + if (meta) parts.push("Meta"); + parts.push(key.toUpperCase()); + return parts.join("+"); + } + + /** + * Normalize key to handle special cases like numbers with shift + * @param {KeyboardEvent} e - The keyboard event + * @returns {String} - Normalized key string + */ + _normalizeKey(e) { + // For digit keys, use e.code to get consistent 'Digit1', 'Digit2', etc. + // This handles Shift+1 = '!' becoming just '1' + if (e.code && e.code.startsWith("Digit")) { + return e.code.replace("Digit", ""); + } + // For numpad, also normalize + if (e.code && e.code.startsWith("Numpad")) { + return e.code.replace("Numpad", ""); + } + // Map e.code to base keys for symbols that change with Shift + // This handles cases like: [ becomes {, / becomes ?, etc. + const codeToKeyMap = { + BracketLeft: "[", + BracketRight: "]", + Slash: "/", + Backslash: "\\", + Semicolon: ";", + Quote: "'", + Comma: ",", + Period: ".", + Minus: "-", + Equal: "=", + Backquote: "`", + }; + if (e.code && codeToKeyMap[e.code]) { + return codeToKeyMap[e.code]; + } + // Otherwise use e.key + return e.key; + } + + /** + * Handle keydown events + */ + _handleKeydown(e) { + if (!this.enabled) return; + + // Don't intercept if user is typing in an input field (unless in HAX edit mode) + const activeElement = globalThis.document.activeElement; + const isInput = + activeElement && + (activeElement.tagName === "INPUT" || + activeElement.tagName === "TEXTAREA" || + activeElement.isContentEditable); + + // Allow shortcuts in HAX editor even when in content editable areas + const inHaxEditor = activeElement && activeElement.closest("hax-body"); + + if (isInput && !inHaxEditor) return; + + // Normalize the key (handles Shift+number keys) + const normalizedKey = this._normalizeKey(e); + + // Generate key for current key combination + const shortcutKey = this._generateKey( + normalizedKey, + e.ctrlKey, + e.shiftKey, + e.altKey, + e.metaKey, + ); + + const shortcut = this.shortcuts.get(shortcutKey); + + if (shortcut && shortcut.condition()) { + e.preventDefault(); + shortcut.callback(e); + } + } + + /** + * Start listening for keyboard shortcuts + */ + enable() { + this.enabled = true; + globalThis.document.addEventListener("keydown", this._boundHandler); + } + + /** + * Stop listening for keyboard shortcuts + */ + disable() { + this.enabled = false; + globalThis.document.removeEventListener("keydown", this._boundHandler); + } + + /** + * Get all registered shortcuts + */ + getShortcuts() { + return Array.from(this.shortcuts.entries()).map(([key, shortcut]) => ({ + key, + ...shortcut, + })); + } + + /** + * Get shortcuts for a specific context + */ + getShortcutsByContext(context) { + return this.getShortcuts().filter( + (s) => s.context === context || s.context === "global", + ); + } + + /** + * Generate a human-readable label for a keyboard shortcut + * @param {Object} options - Shortcut configuration + * @param {String} options.key - Key to press + * @param {Boolean} options.ctrl - Ctrl key required + * @param {Boolean} options.shift - Shift key required + * @param {Boolean} options.alt - Alt key required + * @param {Boolean} options.meta - Meta key required + * @returns {String} - Formatted label (e.g., "Ctrl⇧P") + */ + static generateLabel(options) { + const { + key, + ctrl = false, + shift = false, + alt = false, + meta = false, + } = options; + const parts = []; + if (ctrl) parts.push("Ctrl"); + if (alt) parts.push("Alt"); + if (meta) parts.push("Meta"); + if (shift) parts.push("⇧"); + parts.push(key.toUpperCase()); + return parts.join(""); + } + + /** + * Get a shortcut by its key combination + * @param {String} key - Key to press + * @param {Boolean} ctrl - Ctrl key required + * @param {Boolean} shift - Shift key required + * @param {Boolean} alt - Alt key required + * @param {Boolean} meta - Meta key required + * @returns {Object|null} - Shortcut object or null if not found + */ + getShortcut(key, ctrl = false, shift = false, alt = false, meta = false) { + const shortcutKey = this._generateKey(key, ctrl, shift, alt, meta); + const shortcut = this.shortcuts.get(shortcutKey); + return shortcut ? { key: shortcutKey, ...shortcut } : null; + } + + /** + * Get the label for a specific shortcut + * @param {String} key - Key to press + * @param {Boolean} ctrl - Ctrl key required + * @param {Boolean} shift - Shift key required + * @param {Boolean} alt - Alt key required + * @param {Boolean} meta - Meta key required + * @returns {String|null} - Formatted label or null if not found + */ + getShortcutLabel( + key, + ctrl = false, + shift = false, + alt = false, + meta = false, + ) { + const shortcut = this.getShortcut(key, ctrl, shift, alt, meta); + if (shortcut) { + return HAXCMSKeyboardShortcuts.generateLabel({ + key: shortcut.key, + ctrl: shortcut.ctrl, + shift: shortcut.shift, + alt: shortcut.alt, + meta: shortcut.meta, + }); + } + return null; + } + + /** + * Get all shortcuts formatted for display (e.g., in Merlin) + * @returns {Array} Array of shortcut objects with formatted labels + */ + getShortcutsForDisplay() { + return this.getShortcuts().map((shortcut) => ({ + label: HAXCMSKeyboardShortcuts.generateLabel({ + key: shortcut.key, + ctrl: shortcut.ctrl, + shift: shortcut.shift, + alt: shortcut.alt, + meta: shortcut.meta, + }), + description: shortcut.description, + context: shortcut.context, + key: shortcut.key, + })); + } +} + +// Create singleton instance +globalThis.HAXCMSKeyboardShortcutsManager = + globalThis.HAXCMSKeyboardShortcutsManager || {}; +globalThis.HAXCMSKeyboardShortcutsManager.requestAvailability = () => { + if (!globalThis.HAXCMSKeyboardShortcutsManager.instance) { + globalThis.HAXCMSKeyboardShortcutsManager.instance = + new HAXCMSKeyboardShortcuts(); + } + return globalThis.HAXCMSKeyboardShortcutsManager.instance; +}; + +export const HAXCMSKeyboardShortcutsInstance = + globalThis.HAXCMSKeyboardShortcutsManager.requestAvailability(); diff --git a/elements/haxcms-elements/lib/core/utils/example-restricted-skeleton.json b/elements/haxcms-elements/lib/core/utils/example-restricted-skeleton.json new file mode 100644 index 0000000000..af714da71f --- /dev/null +++ b/elements/haxcms-elements/lib/core/utils/example-restricted-skeleton.json @@ -0,0 +1,102 @@ +{ + "id": "skeleton-a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", + "title": "Restricted Micro-Site", + "description": "Cookie-cutter micro-site where users can only edit page content with limited block types", + "license": "by-sa", + "metadata": { + "site": { + "name": "restricted-micro-site", + "settings": { + "pathauto": false, + "publishPagesOn": true + } + }, + "theme": { + "element": "clean-one", + "variables": { + "hexCode": "#0066cc", + "icon": "icons:home" + } + }, + "platform": { + "delete": false, + "addPage": false, + "outlineDesigner": false, + "pageBreak": false, + "insights": false, + "styleGuide": false, + "manifest": false, + "blocks": ["video-player", "media-image"] + }, + "skeleton": { + "useCaseName": "Restricted Micro-Site Template" + } + }, + "items": [ + { + "id": "item-a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", + "indent": 0, + "slug": "index", + "order": 0, + "parent": null, + "title": "Home", + "description": "Welcome to our micro-site. This content can be edited, but the site structure is locked.", + "content": "

    Welcome to our micro-site. This content can be edited, but the site structure is locked.

    You can only add video and image content to pages.

    ", + "metadata": { + "published": "1" + } + }, + { + "id": "item-b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", + "indent": 0, + "slug": "about", + "order": 1, + "parent": null, + "title": "About", + "description": "Learn more about us here.", + "content": "

    Learn more about us here.

    Add your story, mission, and values on this page.

    ", + "metadata": { + "published": "1" + } + }, + { + "id": "item-c3d4e5f6-a7b8-4c9d-0e1f-2a3b4c5d6e7f", + "indent": 0, + "slug": "services", + "order": 2, + "parent": null, + "title": "Services", + "description": "Our services are listed here.", + "content": "

    Our services are listed here.

    Describe what you offer and how it benefits your audience.

    ", + "metadata": { + "published": "1" + } + }, + { + "id": "item-d4e5f6a7-b8c9-4d0e-1f2a-3b4c5d6e7f8a", + "indent": 0, + "slug": "gallery", + "order": 3, + "parent": null, + "title": "Gallery", + "description": "View our gallery of work.", + "content": "

    View our gallery of work.

    Showcase your projects, products, or portfolio items here.

    ", + "metadata": { + "published": "1" + } + }, + { + "id": "item-e5f6a7b8-c9d0-4e1f-2a3b-4c5d6e7f8a9b", + "indent": 0, + "slug": "contact", + "order": 4, + "parent": null, + "title": "Contact", + "description": "Get in touch with us.", + "content": "

    Get in touch with us.

    Provide your contact information and ways for visitors to reach you.

    ", + "metadata": { + "published": "1" + } + } + ] +} diff --git a/elements/haxcms-elements/lib/core/utils/site-skeleton-generator.js b/elements/haxcms-elements/lib/core/utils/site-skeleton-generator.js new file mode 100644 index 0000000000..7096b76e27 --- /dev/null +++ b/elements/haxcms-elements/lib/core/utils/site-skeleton-generator.js @@ -0,0 +1,549 @@ +/** + * Site Skeleton Generator - Part of HAXcms Elements + * Extracts live site data into reusable skeleton templates + */ + +import { store } from "../haxcms-site-store.js"; +import { toJS } from "mobx"; +import { generateResourceID } from "@haxtheweb/utils/utils.js"; + +export class SiteSkeletonGenerator { + /** + * Generate skeleton from current live site + * @param {boolean} confirmed - Whether user confirmed for large sites + * @returns {Object} Skeleton schema + */ + static async generateFromCurrentSite(confirmed = false) { + const manifest = toJS(store.manifest); + + if (!manifest || !manifest.items) { + throw new Error("No site data available to generate skeleton"); + } + + // Check for large sites and get confirmation + if (manifest.items.length > 20 && !confirmed) { + const shouldContinue = confirm( + `This site has ${manifest.items.length} pages. Generating a skeleton may take a while and create a large file. Continue?`, + ); + if (!shouldContinue) { + throw new Error("Skeleton generation cancelled by user"); + } + } + + const themeConfig = this.extractThemeConfiguration(manifest); + const structure = await this.extractSiteStructure(manifest.items); + const files = await this.extractSiteFiles(); + + // Format skeleton to match createSite API structure directly + const skeleton = { + // Metadata for discoverability and template management + meta: { + name: this.getSiteMetaName(manifest), + description: this.getSiteDescription(manifest), + version: "1.0.0", + created: new Date().toISOString(), + type: "skeleton", + sourceUrl: globalThis.location.href, + // Fields used by app-hax v2 presentation layer + useCaseTitle: this.getSiteMetaName(manifest), + useCaseDescription: this.getSiteDescription(manifest), + useCaseImage: "", // optional preview image; can be set later + category: this.extractSiteCategories(manifest) || [], + tags: this.extractSiteTags(manifest) || [], + attributes: [], // optional icon badges [{icon, tooltip}] + }, + + // Direct mapping to createSite API format + site: { + name: this.getSiteMetaName(manifest), + description: this.getSiteDescription(manifest), + theme: themeConfig.element || "clean-one", + }, + + build: { + type: "skeleton", + structure: "from-skeleton", + items: structure, + files: files.map((filePath) => ({ path: filePath })), + }, + + theme: { + // Include theme variables from original site if available + ...(themeConfig.variables || {}), + // Include theme settings/colors from original + ...(themeConfig.settings || {}), + }, + + // Additional metadata for template system + _skeleton: { + originalMetadata: this.extractSiteMetadata(manifest), + originalSettings: this.extractSiteSettings(manifest), + fullThemeConfig: themeConfig, + }, + }; + + return skeleton; + } + + /** + * Get site meta name safely + * @param {Object} manifest - Site manifest + * @returns {string} Site name + */ + static getSiteMetaName(manifest) { + if ( + manifest.metadata && + manifest.metadata.site && + manifest.metadata.site.name + ) { + return manifest.metadata.site.name; + } + return "Untitled Skeleton"; + } + + /** + * Get site description safely + * @param {Object} manifest - Site manifest + * @returns {string} Site description + */ + static getSiteDescription(manifest) { + if (manifest.description) { + return `Template based on ${manifest.description}`; + } + if (manifest.title) { + return `Template based on ${manifest.title}`; + } + return "Template based on HAX Site"; + } + + /** + * Extract site metadata for template + * @param {Object} manifest - Site manifest + * @returns {Object} Site metadata template + */ + static extractSiteMetadata(manifest) { + const metadata = manifest.metadata || {}; + const siteData = metadata.site || {}; + + return { + site: { + category: siteData.category || [], + tags: siteData.tags || [], + settings: this.cleanSiteSettings(siteData.settings || {}), + }, + licensing: metadata.licensing || {}, + node: metadata.node || {}, + platform: metadata.platform || {}, + }; + } + + /** + * Clean site settings removing instance-specific data + * @param {Object} settings - Original settings + * @returns {Object} Cleaned settings + */ + static cleanSiteSettings(settings) { + const cleaned = { ...settings }; + // Remove instance-specific timestamps + delete cleaned.created; + delete cleaned.updated; + return cleaned; + } + + /** + * Extract theme configuration + * @param {Object} manifest - Site manifest + * @returns {Object} Theme configuration + */ + static extractThemeConfiguration(manifest) { + const metadata = manifest.metadata || {}; + const theme = metadata.theme || {}; + + return { + element: theme.element || "clean-one", + variables: theme.variables || {}, + settings: { + hexCode: theme.hexCode, + cssVariable: theme.cssVariable, + icon: theme.icon, + image: theme.image, + // Extract any other theme properties + ...this.extractCustomThemeProperties(theme), + }, + }; + } + + /** + * Extract custom theme properties + * @param {Object} theme - Theme object + * @returns {Object} Custom properties + */ + static extractCustomThemeProperties(theme) { + const custom = {}; + const standardProps = [ + "element", + "variables", + "hexCode", + "cssVariable", + "icon", + "image", + ]; + + Object.keys(theme).forEach((key) => { + if (!standardProps.includes(key)) { + custom[key] = theme[key]; + } + }); + + return custom; + } + + /** + * Extract site structure with UUIDs for relationships and content + * @param {Array} items - Site items/pages + * @returns {Array} Structure template + */ + static async extractSiteStructure(items) { + // Create UUID mappings for items + const uuidMap = new Map(); + items.forEach((item) => { + uuidMap.set(item.id, generateResourceID()); + }); + + const structure = []; + + for (let i = 0; i < items.length; i++) { + const item = items[i]; + + // Get page content using store helper method + let content = ""; + try { + // Use the store's loadItemContent method to get page content + content = await store.loadItemContent(item.id); + } catch (error) { + console.warn(`Could not fetch content for page ${item.id}:`, error); + content = ""; + } + + structure.push({ + id: uuidMap.get(item.id), + title: item.title, + slug: item.slug, + order: item.order || i, + parent: item.parent ? uuidMap.get(item.parent) : null, + indent: item.indent || 0, + content: content, + metadata: { + published: this.getMetadataValue(item, "published", true), + hideInMenu: this.getMetadataValue(item, "hideInMenu", false), + tags: this.getMetadataValue(item, "tags", []), + ...this.extractRelevantMetadata(item.metadata || {}), + }, + }); + } + + return structure; + } + + /** + * Extract file references in the site + * @returns {Array} Array of file paths referenced in the site content + */ + static async extractSiteFiles() { + const fileReferences = new Set(); + const manifest = toJS(store.manifest); + const filesBaseUrl = "files/"; + + // Get all page content and look for file references + for (const item of manifest.items) { + try { + const content = await store.loadItemContent(item.id); + if (content) { + // Look for local file references in the content + const fileRefs = this.extractFileReferences(content, filesBaseUrl); + fileRefs.forEach((ref) => fileReferences.add(ref.path)); + } + } catch (error) { + console.warn(`Could not process files for page ${item.id}:`, error); + } + } + + return Array.from(fileReferences); + } + + /** + * Extract file references from content + * @param {string} content - HTML content + * @param {string} filesBaseUrl - Base URL for files folder + * @returns {Array} Array of file references + */ + static extractFileReferences(content, filesBaseUrl) { + const fileRefs = []; + // Look for attributes like src="files/..." or href="files/..." etc. + // Ensure the path ends with a file extension + const fileRegex = /\w+="files\/([^"'\s>]+\.[a-zA-Z0-9]+)"/gi; + + let match; + while ((match = fileRegex.exec(content)) !== null) { + const filePath = match[1]; + + fileRefs.push({ + path: filePath, + }); + } + + return fileRefs; + } + + /** + * Safely get metadata value + * @param {Object} item - Site item + * @param {string} key - Metadata key + * @param {*} defaultValue - Default value + * @returns {*} Metadata value + */ + static getMetadataValue(item, key, defaultValue) { + if (item.metadata && item.metadata[key] !== undefined) { + return item.metadata[key]; + } + return defaultValue; + } + + /** + * Extract relevant metadata for templates + * @param {Object} metadata - Original metadata + * @returns {Object} Template-relevant metadata + */ + static extractRelevantMetadata(metadata) { + const relevant = {}; + const preserveFields = [ + "wordCount", + "difficulty", + "audience", + "contentType", + ]; + + preserveFields.forEach((field) => { + if (metadata[field] !== undefined) { + relevant[field] = metadata[field]; + } + }); + + return relevant; + } + + /** + * Extract site categories for template discoverability + * @param {Object} manifest - Site manifest + * @returns {Array} Categories array + */ + static extractSiteCategories(manifest) { + const metadata = manifest.metadata || {}; + const siteData = metadata.site || {}; + return siteData.category || []; + } + + /** + * Extract site tags for template discoverability + * @param {Object} manifest - Site manifest + * @returns {Array} Tags array + */ + static extractSiteTags(manifest) { + const metadata = manifest.metadata || {}; + const siteData = metadata.site || {}; + return siteData.tags || []; + } + + /** + * Extract site settings + * @param {Object} manifest - Site manifest + * @returns {Object} Site settings template + */ + static extractSiteSettings(manifest) { + const metadata = manifest.metadata || {}; + const siteData = metadata.site || {}; + const settings = siteData.settings || {}; + + const extractedSettings = {}; + + // Only include non-empty settings + Object.keys(settings).forEach((key) => { + const value = settings[key]; + if ( + value !== undefined && + value !== null && + value !== "" && + !(Array.isArray(value) && value.length === 0) && + !(typeof value === "object" && Object.keys(value).length === 0) + ) { + extractedSettings[key] = value; + } + }); + + return extractedSettings; + } + + /** + * Generate downloadable skeleton file + * @param {Object} skeleton - Skeleton schema + * @returns {Blob} Downloadable file + */ + static generateDownloadableFile(skeleton) { + const content = JSON.stringify(skeleton, null, 2); + return new Blob([content], { type: "application/json" }); + } + + /** + * Download skeleton as file + * @param {Object} skeleton - Skeleton schema + * @param {string} filename - Download filename + */ + static downloadSkeleton(skeleton, filename) { + const defaultFilename = `${skeleton.meta.name.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-skeleton.json`; + const blob = this.generateDownloadableFile(skeleton); + const url = URL.createObjectURL(blob); + + const a = document.createElement("a"); + a.href = url; + a.download = filename || defaultFilename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } + + /** + * Convert skeleton to app-hax v2 format (now direct since skeleton matches API) + * @param {Object} skeleton - Skeleton schema + * @param {Object} customizations - User customizations + * @returns {Object} App-hax v2 compatible format + */ + static toAppHaxFormat(skeleton, customizations = {}) { + // Since skeleton now directly matches createSite API format, + // just apply any user customizations and return + const apiData = { + site: { + name: customizations.siteName || skeleton.site.name, + description: + customizations.siteDescription || skeleton.site.description, + theme: customizations.theme || skeleton.site.theme, + }, + build: { + ...skeleton.build, + // Update items with new timestamps for creation + items: skeleton.build.items.map((item) => ({ + ...item, + metadata: { + ...item.metadata, + created: new Date().toISOString(), + updated: new Date().toISOString(), + }, + })), + }, + theme: { + ...skeleton.theme, + // Apply any user customizations + ...(customizations.color ? { color: customizations.color } : {}), + ...(customizations.icon ? { icon: customizations.icon } : {}), + ...customizations.themeSettings, + }, + }; + + return apiData; + } + + /** + * Generate files from skeleton structure + * @param {Object} skeleton - Skeleton schema + * @param {Object} customizations - User customizations + * @returns {Object} File map + */ + static generateFilesFromSkeleton(skeleton, customizations = {}) { + const files = {}; + + // Add page content files + skeleton.structure.forEach((item) => { + const location = `pages/${item.slug}/index.html`; + files[location] = item.content || ""; + }); + + // Reference skeleton files for backend to fetch + if (skeleton.files && skeleton.files.length > 0) { + skeleton.files.forEach((filePath) => { + files[`files/${filePath}`] = `[SKELETON_FILE_REFERENCE:${filePath}]`; + }); + } + + return files; + } + + /** + * Rewrite skeleton UUIDs for new site creation + * @param {Object} skeleton - Original skeleton + * @returns {Object} Skeleton with new UUIDs + */ + static rewriteSkeletonUuids(skeleton) { + const oldToNewMap = new Map(); + + // Create new UUIDs for all items first + skeleton.structure.forEach((item) => { + if (item.id) { + oldToNewMap.set(item.id, generateResourceID()); + } + }); + + // Rewrite structure with new UUIDs and update parent references + const newStructure = skeleton.structure.map((item) => { + const newItem = { ...item }; + + if (item.id) { + newItem.id = oldToNewMap.get(item.id); + } + + if (item.parent && oldToNewMap.has(item.parent)) { + newItem.parent = oldToNewMap.get(item.parent); + } + + return newItem; + }); + + // Also rewrite UUIDs in theme settings regions if they exist + const newTheme = { ...skeleton.theme }; + if (newTheme.settings && newTheme.settings.regions) { + const newRegions = { ...newTheme.settings.regions }; + Object.keys(newRegions).forEach((regionKey) => { + const region = newRegions[regionKey]; + if (region && typeof region === "object") { + // Check if region has UUID references that need updating + Object.keys(region).forEach((prop) => { + const value = region[prop]; + if (typeof value === "string" && oldToNewMap.has(value)) { + region[prop] = oldToNewMap.get(value); + } else if (Array.isArray(value)) { + // Handle arrays that might contain UUIDs + region[prop] = value.map((item) => + typeof item === "string" && oldToNewMap.has(item) + ? oldToNewMap.get(item) + : item, + ); + } + }); + } + }); + newTheme.settings.regions = newRegions; + } + + return { + ...skeleton, + structure: newStructure, + theme: newTheme, + meta: { + ...skeleton.meta, + created: new Date().toISOString(), + sourceUuids: "rewritten", + }, + }; + } +} + +export default SiteSkeletonGenerator; diff --git a/elements/haxcms-elements/lib/hax-elements-registry.json b/elements/haxcms-elements/lib/hax-elements-registry.json index 435002bb39..88d3ee6ef4 100644 --- a/elements/haxcms-elements/lib/hax-elements-registry.json +++ b/elements/haxcms-elements/lib/hax-elements-registry.json @@ -1,4 +1,5 @@ { + "-omfofks": "label", "a11y-collapse": "A11y Collapse", "a11y-collapse-group": "A11y Collapse Group", "a11y-details": "Accessible Details Button", @@ -14,10 +15,13 @@ "audio": "Audio", "audio-player": "caption", "author-card": "Author Card", + "awesome-explosion": "Awesome Explosion", "b": "Bold", "beaker-broker": "Beaker Broker", + "bkXE4h-7": "title", "block-quote": "Block Quote", "blockquote": "Block quote", + "C-KOWwKf": "label", "caption": "Caption", "check-it-out": "Check It Out", "citation-element": "title", @@ -33,6 +37,7 @@ "collection-row": "Collection Row", "count-up": "Count up", "course-model": "3d Model", + "Ctdgwp-s": "Ctdgwp S", "d-d-docs": "Design, Develop, Destroy", "date-card": "Date Card", "dd": "Data definition", @@ -79,7 +84,7 @@ "hax-context-item-textop": "Hax Context Item Textop", "hax-element-list-selector": "Hax Element List Selector", "hax-logo": "Hax Logo", - "hax-store": "Basic Image", + "hax-store": "Image Gallery", "haxcms-site-disqus": "Haxcms Site Disqus", "haxcms-site-editor-ui": "Load HAXSchema", "hex-picker": "Hex Picker", @@ -88,7 +93,7 @@ "iframe-loader": "Iframe Loader", "image-compare-slider": "Image Compare Slider", "inline-audio": "Inline Audio", - "instruction-card": "Basic Image", + "instruction-card": "Image Gallery", "la-tex": "La Tex", "learning-component": "Learning Component", "lecture-player": "caption", @@ -135,7 +140,6 @@ "relative-heading": "Relative heading", "responsive-iframe": "Responsive iframe", "retro-card": "Retro card", - "rM-r93Ak": "label", "rpg-character": "Rpg Character", "runkit-embed": "Runkit Embed", "screen-recorder": "Screen Recorder", @@ -150,6 +154,7 @@ "simple-toolbar": "Tag name", "simple-wc": "Simple Wc", "site-active-title": "HAXcms active title", + "site-available-themes": "Available Themes", "site-breadcrumb": "HAXcms active title", "site-children-block": "HAXcms: child block", "site-collection-list": "Site Collection List", @@ -178,7 +183,6 @@ "u": "Underline", "ul": "Bulleted list", "un-sdg": "Un Sdg", - "undo-manager": "Figure label", "unity-webgl": "Unity Player", "user-action": "User Action", "video": "Video", @@ -188,4 +192,4 @@ "web-container-doc-player": "Web Container Doc Player", "wikipedia-query": "title", "worksheet-download": "Worksheet Download" -} +} \ No newline at end of file diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme-thumb.jpg new file mode 100644 index 0000000000..2202e3538b Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme.jpg new file mode 100644 index 0000000000..8fc00d8c44 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-bootstrap-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-bootstrap-theme-thumb.jpg new file mode 100644 index 0000000000..90298d900a Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-bootstrap-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-bootstrap-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-bootstrap-theme.jpg new file mode 100644 index 0000000000..5061745eeb Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-bootstrap-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-clean-one-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-one-thumb.jpg new file mode 100644 index 0000000000..bdf9745111 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-one-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-clean-one.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-one.jpg new file mode 100644 index 0000000000..1f177ead54 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-one.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme-thumb.jpg new file mode 100644 index 0000000000..cb85076f8b Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme.jpg new file mode 100644 index 0000000000..19ebccef73 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-clean-two-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-two-thumb.jpg new file mode 100644 index 0000000000..0442c019c8 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-two-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-clean-two.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-two.jpg new file mode 100644 index 0000000000..2a7491a3e3 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-clean-two.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-collections-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-collections-theme-thumb.jpg new file mode 100644 index 0000000000..ff33724949 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-collections-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-collections-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-collections-theme.jpg new file mode 100644 index 0000000000..1a6a2bce8a Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-collections-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-ddd-brochure-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-ddd-brochure-theme-thumb.jpg new file mode 100644 index 0000000000..72d7072103 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-ddd-brochure-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-ddd-brochure-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-ddd-brochure-theme.jpg new file mode 100644 index 0000000000..b9daa3eda8 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-ddd-brochure-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-glossy-portfolio-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-glossy-portfolio-theme-thumb.jpg new file mode 100644 index 0000000000..35364dce3b Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-glossy-portfolio-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-glossy-portfolio-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-glossy-portfolio-theme.jpg new file mode 100644 index 0000000000..11262fdd70 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-glossy-portfolio-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-blank-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-blank-theme-thumb.jpg new file mode 100644 index 0000000000..8f01eba071 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-blank-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-blank-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-blank-theme.jpg new file mode 100644 index 0000000000..7b8475e007 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-blank-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-json-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-json-theme-thumb.jpg new file mode 100644 index 0000000000..805aa0fd03 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-json-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-json-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-json-theme.jpg new file mode 100644 index 0000000000..8483ecb675 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-json-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-print-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-print-theme-thumb.jpg new file mode 100644 index 0000000000..24c3b1ae91 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-print-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-print-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-print-theme.jpg new file mode 100644 index 0000000000..b6e353f511 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxcms-print-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxma-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxma-theme-thumb.jpg new file mode 100644 index 0000000000..62b2bc33aa Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxma-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxma-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxma-theme.jpg new file mode 100644 index 0000000000..85e0cadd64 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxma-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxor-slevin-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxor-slevin-thumb.jpg new file mode 100644 index 0000000000..d6113622b9 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxor-slevin-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-haxor-slevin.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-haxor-slevin.jpg new file mode 100644 index 0000000000..04413c1f86 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-haxor-slevin.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-journey-sidebar-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-sidebar-theme-thumb.jpg new file mode 100644 index 0000000000..a90c80a659 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-sidebar-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-journey-sidebar-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-sidebar-theme.jpg new file mode 100644 index 0000000000..07df2bdb5e Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-sidebar-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-journey-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-theme-thumb.jpg new file mode 100644 index 0000000000..d9bd1579ae Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-journey-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-theme.jpg new file mode 100644 index 0000000000..9c843277be Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-journey-topbar-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-topbar-theme-thumb.jpg new file mode 100644 index 0000000000..c62cb91e4e Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-topbar-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-journey-topbar-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-topbar-theme.jpg new file mode 100644 index 0000000000..ad54025870 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-journey-topbar-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-learn-two-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-learn-two-theme-thumb.jpg new file mode 100644 index 0000000000..68b7a78c32 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-learn-two-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-learn-two-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-learn-two-theme.jpg new file mode 100644 index 0000000000..a401dc3def Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-learn-two-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-outline-player-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-outline-player-thumb.jpg new file mode 100644 index 0000000000..e9e472832d Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-outline-player-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-outline-player.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-outline-player.jpg new file mode 100644 index 0000000000..4b46b822d4 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-outline-player.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-sidebar-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-sidebar-thumb.jpg new file mode 100644 index 0000000000..aefdf35c90 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-sidebar-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-sidebar.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-sidebar.jpg new file mode 100644 index 0000000000..ee56cb3322 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-sidebar.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-theme-thumb.jpg new file mode 100644 index 0000000000..f7ac4f8f1f Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-theme.jpg new file mode 100644 index 0000000000..18679638ca Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-invent-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-invent-theme-thumb.jpg new file mode 100644 index 0000000000..475cc0e499 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-invent-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-invent-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-invent-theme.jpg new file mode 100644 index 0000000000..9ddf73b800 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-invent-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-theme-thumb.jpg new file mode 100644 index 0000000000..13c5a8d1db Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-theme.jpg new file mode 100644 index 0000000000..cc62af7bb9 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-polaris-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-simple-blog-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-simple-blog-thumb.jpg new file mode 100644 index 0000000000..606bb5ab98 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-simple-blog-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-simple-blog.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-simple-blog.jpg new file mode 100644 index 0000000000..99f955e0d7 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-simple-blog.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-spacebook-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-spacebook-theme-thumb.jpg new file mode 100644 index 0000000000..520d469f42 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-spacebook-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-spacebook-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-spacebook-theme.jpg new file mode 100644 index 0000000000..c94b80b112 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-spacebook-theme.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-best-themes-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-best-themes-thumb.jpg new file mode 100644 index 0000000000..71b7b006c4 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-best-themes-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-best-themes.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-best-themes.jpg new file mode 100644 index 0000000000..3e92a6d63b Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-best-themes.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-outlet-themes-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-outlet-themes-thumb.jpg new file mode 100644 index 0000000000..450b4f51a1 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-outlet-themes-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-outlet-themes.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-outlet-themes.jpg new file mode 100644 index 0000000000..ada9eb7453 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-outlet-themes.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-productionz-themes-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-productionz-themes-thumb.jpg new file mode 100644 index 0000000000..53ee2945f1 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-productionz-themes-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-productionz-themes.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-productionz-themes.jpg new file mode 100644 index 0000000000..334a737a68 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-productionz-themes.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-resume-themes-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-resume-themes-thumb.jpg new file mode 100644 index 0000000000..ff1a8fdc61 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-resume-themes-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-resume-themes.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-resume-themes.jpg new file mode 100644 index 0000000000..fe5e4b6ca8 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-resume-themes.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-themes-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-themes-thumb.jpg new file mode 100644 index 0000000000..37f060f032 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-themes-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-themes.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-themes.jpg new file mode 100644 index 0000000000..5be5870a64 Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-terrible-themes.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-training-theme-thumb.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-training-theme-thumb.jpg new file mode 100644 index 0000000000..219eadc8bb Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-training-theme-thumb.jpg differ diff --git a/elements/haxcms-elements/lib/theme-screenshots/theme-training-theme.jpg b/elements/haxcms-elements/lib/theme-screenshots/theme-training-theme.jpg new file mode 100644 index 0000000000..6f1c563ddf Binary files /dev/null and b/elements/haxcms-elements/lib/theme-screenshots/theme-training-theme.jpg differ diff --git a/elements/haxcms-elements/lib/themes.json b/elements/haxcms-elements/lib/themes.json index d5889d4989..eca0755886 100644 --- a/elements/haxcms-elements/lib/themes.json +++ b/elements/haxcms-elements/lib/themes.json @@ -3,180 +3,365 @@ "element": "app-hax-theme", "path": "@haxtheweb/app-hax/lib/app-hax-theme.js", "name": "8-bit Overworld theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-app-hax-theme-thumb.jpg", + "description": "Start with a blank site using the 8-bit Overworld theme", + "category": [ + "Course" + ], + "hidden": false, + "terrible": false }, "bootstrap-theme": { "element": "bootstrap-theme", "path": "@haxtheweb/bootstrap-theme/bootstrap-theme.js", "name": "Bootstrap Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-bootstrap-theme-thumb.jpg", + "description": "Start with a blank site using the Bootstrap Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "clean-one": { "element": "clean-one", "path": "@haxtheweb/clean-one/clean-one.js", "name": "Clean One", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-clean-one-thumb.jpg", + "description": "Start with a blank site using the Clean One", + "category": [ + "Course" + ], + "hidden": false, + "terrible": false }, "clean-portfolio-theme": { "element": "clean-portfolio-theme", "path": "@haxtheweb/clean-portfolio-theme/clean-portfolio-theme.js", "name": "Clean Portfolio Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-clean-portfolio-theme-thumb.jpg", + "description": "Start with a blank site using the Clean Portfolio Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "clean-two": { "element": "clean-two", "path": "@haxtheweb/clean-two/clean-two.js", "name": "Clean Two", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-clean-two-thumb.jpg", + "description": "Start with a blank site using the Clean Two", + "category": [ + "Course" + ], + "hidden": false, + "terrible": false }, "collections-theme": { "element": "collections-theme", "path": "@haxtheweb/collection-list/lib/collections-theme.js", "name": "Collections Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-collections-theme-thumb.jpg", + "description": "Start with a blank site using the Collections Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "ddd-brochure-theme": { "element": "ddd-brochure-theme", "path": "@haxtheweb/d-d-d/lib/ddd-brochure-theme.js", "name": "Brochure Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-ddd-brochure-theme-thumb.jpg", + "description": "Start with a blank site using the Brochure Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "glossy-portfolio-theme": { "element": "glossy-portfolio-theme", "path": "@haxtheweb/glossy-portfolio-theme/glossy-portfolio-theme.js", "name": "Glossy Portfolio Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-glossy-portfolio-theme-thumb.jpg", + "description": "Start with a blank site using the Glossy Portfolio Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "haxcms-blank-theme": { "element": "haxcms-blank-theme", "path": "@haxtheweb/haxcms-elements/lib/haxcms-blank-theme.js", "name": "Haxcms Blank Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-haxcms-blank-theme-thumb.jpg", + "description": "Start with a blank site using the Haxcms Blank Theme", + "category": [ + "Website" + ], + "hidden": true, + "terrible": false }, "haxcms-json-theme": { "element": "haxcms-json-theme", "path": "@haxtheweb/haxcms-elements/lib/haxcms-json-theme.js", "name": "Haxcms Json Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-haxcms-json-theme-thumb.jpg", + "description": "Start with a blank site using the Haxcms Json Theme", + "category": [ + "Website" + ], + "hidden": true, + "terrible": false }, "haxcms-print-theme": { "element": "haxcms-print-theme", "path": "@haxtheweb/haxcms-elements/lib/haxcms-print-theme.js", "name": "Haxcms Print Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-haxcms-print-theme-thumb.jpg", + "description": "Start with a blank site using the Haxcms Print Theme", + "category": [ + "Website" + ], + "hidden": true, + "terrible": false }, "haxma-theme": { "element": "haxma-theme", "path": "@haxtheweb/haxma-theme/haxma-theme.js", "name": "HAXma doc theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-haxma-theme-thumb.jpg", + "description": "Start with a blank site using the HAXma doc theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "haxor-slevin": { "element": "haxor-slevin", "path": "@haxtheweb/haxor-slevin/haxor-slevin.js", "name": "Haxor Slevin", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-haxor-slevin-thumb.jpg", + "description": "Start with a blank site using the Haxor Slevin", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "journey-sidebar-theme": { "element": "journey-sidebar-theme", "path": "@haxtheweb/journey-theme/lib/journey-sidebar-theme.js", "name": "Journey Sidebar theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-journey-sidebar-theme-thumb.jpg", + "description": "Start with a blank site using the Journey Sidebar theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "journey-theme": { "element": "journey-theme", "path": "@haxtheweb/journey-theme/journey-theme.js", "name": "Journey Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-journey-theme-thumb.jpg", + "description": "Start with a blank site using the Journey Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "journey-topbar-theme": { "element": "journey-topbar-theme", "path": "@haxtheweb/journey-theme/lib/journey-topbar-theme.js", "name": "Journey Topbar Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-journey-topbar-theme-thumb.jpg", + "description": "Start with a blank site using the Journey Topbar Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "learn-two-theme": { "element": "learn-two-theme", "path": "@haxtheweb/learn-two-theme/learn-two-theme.js", "name": "Learn theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-learn-two-theme-thumb.jpg", + "description": "Start with a blank site using the Learn theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "outline-player": { "element": "outline-player", "path": "@haxtheweb/outline-player/outline-player.js", "name": "Outline Player", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-outline-player-thumb.jpg", + "description": "Start with a blank site using the Outline Player", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "polaris-flex-sidebar": { "element": "polaris-flex-sidebar", "path": "@haxtheweb/polaris-theme/lib/polaris-flex-sidebar.js", "name": "Polaris Flex Sidebar", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-sidebar-thumb.jpg", + "description": "Start with a blank site using the Polaris Flex Sidebar", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "polaris-flex-theme": { "element": "polaris-flex-theme", "path": "@haxtheweb/polaris-theme/lib/polaris-flex-theme.js", "name": "Polaris Flex Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-polaris-flex-theme-thumb.jpg", + "description": "Start with a blank site using the Polaris Flex Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "polaris-invent-theme": { "element": "polaris-invent-theme", "path": "@haxtheweb/polaris-theme/lib/polaris-invent-theme.js", "name": "Polaris Invent Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-polaris-invent-theme-thumb.jpg", + "description": "Start with a blank site using the Polaris Invent Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "polaris-theme": { "element": "polaris-theme", "path": "@haxtheweb/polaris-theme/polaris-theme.js", "name": "Polaris Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-polaris-theme-thumb.jpg", + "description": "Start with a blank site using the Polaris Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "simple-blog": { "element": "simple-blog", "path": "@haxtheweb/simple-blog/simple-blog.js", "name": "Simple Blog", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-simple-blog-thumb.jpg", + "description": "Start with a blank site using the Simple Blog", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "spacebook-theme": { "element": "spacebook-theme", "path": "@haxtheweb/spacebook-theme/spacebook-theme.js", "name": "Space Book theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-spacebook-theme-thumb.jpg", + "description": "Start with a blank site using the Space Book theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false }, "terrible-best-themes": { "element": "terrible-best-themes", "path": "@haxtheweb/terrible-themes/lib/terrible-best-themes.js", "name": "Terrible 90s - hockey player theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-terrible-best-themes-thumb.jpg", + "description": "Start with a blank site using the Terrible 90s - hockey player theme", + "category": [ + "Fun", + "Website" + ], + "hidden": false, + "terrible": true }, "terrible-outlet-themes": { "element": "terrible-outlet-themes", "path": "@haxtheweb/terrible-themes/lib/terrible-outlet-themes.js", "name": "Terrible 90s - blog theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-terrible-outlet-themes-thumb.jpg", + "description": "Start with a blank site using the Terrible 90s - blog theme", + "category": [ + "Fun", + "Website" + ], + "hidden": false, + "terrible": true }, "terrible-productionz-themes": { "element": "terrible-productionz-themes", "path": "@haxtheweb/terrible-themes/lib/terrible-productionz-themes.js", "name": "Terrible 90s - productionz theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/GreyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-terrible-productionz-themes-thumb.jpg", + "description": "Start with a blank site using the Terrible 90s - productionz theme", + "category": [ + "Fun", + "Website" + ], + "hidden": false, + "terrible": true }, "terrible-resume-themes": { "element": "terrible-resume-themes", "path": "@haxtheweb/terrible-themes/lib/terrible-resume-themes.js", "name": "Terrible 90s - resume theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/PartyStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-terrible-resume-themes-thumb.jpg", + "description": "Start with a blank site using the Terrible 90s - resume theme", + "category": [ + "Fun", + "Website" + ], + "hidden": false, + "terrible": true }, "terrible-themes": { "element": "terrible-themes", "path": "@haxtheweb/terrible-themes/terrible-themes.js", "name": "Terrible 90s - Room 407 theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-terrible-themes-thumb.jpg", + "description": "Start with a blank site using the Terrible 90s - Room 407 theme", + "category": [ + "Fun", + "Website" + ], + "hidden": false, + "terrible": true }, "training-theme": { "element": "training-theme", "path": "@haxtheweb/training-theme/training-theme.js", "name": "Training Theme", - "thumbnail": "build/es6/node_modules/@haxtheweb/app-hax/lib/assets/images/BlueStyle.svg" + "thumbnail": "@haxtheweb/haxcms-elements/lib/theme-screenshots/theme-training-theme-thumb.jpg", + "description": "Start with a blank site using the Training Theme", + "category": [ + "Website" + ], + "hidden": false, + "terrible": false } -} +} \ No newline at end of file diff --git a/elements/haxcms-elements/lib/ui-components/active-item/site-active-tags.js b/elements/haxcms-elements/lib/ui-components/active-item/site-active-tags.js index 95d59eadf6..f142f4aa86 100644 --- a/elements/haxcms-elements/lib/ui-components/active-item/site-active-tags.js +++ b/elements/haxcms-elements/lib/ui-components/active-item/site-active-tags.js @@ -3,6 +3,7 @@ * @license Apache-2.0, see License.md for full text. */ import { LitElement, html, css } from "lit"; +import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; import { autorun, toJS } from "mobx"; import "@haxtheweb/simple-fields/lib/simple-tags.js"; @@ -13,7 +14,7 @@ import "@haxtheweb/simple-fields/lib/simple-tags.js"; * * @demo demo/index.html */ -class SiteActiveTags extends LitElement { +class SiteActiveTags extends I18NMixin(LitElement) { /** * Store the tag name to make it easier to obtain directly. */ @@ -27,6 +28,11 @@ class SiteActiveTags extends LitElement { :host { display: block; } + .tag-container { + display: flex; + align-items: center; + gap: var(--ddd-spacing-2); + } a { text-decoration: none; } @@ -40,23 +46,25 @@ class SiteActiveTags extends LitElement { * LitElement */ render() { - return html`
    ${ - this.tags && this.tags != "" && this.tags.split - ? this.tags.split(",").map( - (tag) => - html` - - `, - ) - : `` - }
    `; + return html` +
    + ${this.tags && this.tags != "" && this.tags.split + ? this.tags.split(",").map( + (tag) => + html` + + `, + ) + : ``} +
    + `; } testEditMode(e) { @@ -98,6 +106,10 @@ class SiteActiveTags extends LitElement { this.accentColor = null; this.tags = null; this.__disposer = []; + this.registerLocalization({ + context: this, + basePath: import.meta.url, + }); autorun((reaction) => { this.tags = toJS(store.activeTags); this.__disposer.push(reaction); diff --git a/elements/haxcms-elements/lib/ui-components/active-item/site-active-title.js b/elements/haxcms-elements/lib/ui-components/active-item/site-active-title.js index 7a1921aa03..1ab5839a05 100644 --- a/elements/haxcms-elements/lib/ui-components/active-item/site-active-title.js +++ b/elements/haxcms-elements/lib/ui-components/active-item/site-active-title.js @@ -9,6 +9,7 @@ import { } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; import { autorun, toJS } from "mobx"; import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; +import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; /** * `site-active-title` @@ -16,7 +17,7 @@ import "@haxtheweb/simple-icon/lib/simple-icon-lite.js"; * * @demo demo/index.html */ -class SiteActiveTitle extends LitElement { +class SiteActiveTitle extends I18NMixin(LitElement) { /** * Store the tag name to make it easier to obtain directly. */ @@ -32,6 +33,7 @@ class SiteActiveTitle extends LitElement { site-active-title { display: block; text-align: start; + position: relative; } site-active-title[edit-mode]:hover { cursor: pointer; @@ -39,23 +41,36 @@ class SiteActiveTitle extends LitElement { transition: 0.2s outline-width ease-in-out; outline-offset: 8px; } + site-active-title h1 { + margin: 0; + display: flex; + align-items: center; + gap: 8px; + } h1 .site-active-title-icon { --simple-icon-height: 32px; --simple-icon-width: 32px; - margin-right: 8px; - vertical-align: middle; + flex-shrink: 0; + } + site-active-title .title-wrapper { + display: flex; + align-items: center; + gap: 8px; + flex: 1; }

    - ${this.icon - ? html` - - ` - : ``} - ${this.__title} +
    + ${this.icon + ? html` + + ` + : ``} + ${this.__title} +

    `; } @@ -167,6 +182,9 @@ class SiteActiveTitle extends LitElement { reflect: true, attribute: "edit-mode", }, + t: { + type: Object, + }, }; } /** @@ -202,6 +220,10 @@ class SiteActiveTitle extends LitElement { this.__title = ""; this.icon = null; this.__disposer = []; + this.registerLocalization({ + context: this, + basePath: import.meta.url, + }); autorun((reaction) => { this.editMode = toJS(store.editMode); this.__disposer.push(reaction); diff --git a/elements/lesson-overview/lesson-overview.js b/elements/haxcms-elements/lib/ui-components/lesson-overview/lesson-overview.js similarity index 100% rename from elements/lesson-overview/lesson-overview.js rename to elements/haxcms-elements/lib/ui-components/lesson-overview/lesson-overview.js diff --git a/elements/haxcms-elements/lib/ui-components/lesson-overview/lib/.gitkeep b/elements/haxcms-elements/lib/ui-components/lesson-overview/lib/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/elements/lesson-overview/lib/lesson-highlight.haxProperties.json b/elements/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-highlight.haxProperties.json similarity index 94% rename from elements/lesson-overview/lib/lesson-highlight.haxProperties.json rename to elements/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-highlight.haxProperties.json index 5a5fc4576b..a91440c4fc 100644 --- a/elements/lesson-overview/lib/lesson-highlight.haxProperties.json +++ b/elements/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-highlight.haxProperties.json @@ -7,13 +7,7 @@ "description": "Quick overview of a thing", "icon": "communication:business", "color": "blue", - "tags": [ - "Instructional", - "layout", - "highlight", - "summary", - "overview" - ], + "tags": ["Instructional", "layout", "highlight", "summary", "overview"], "handles": [], "meta": { "author": "HAXTheWeb core team" diff --git a/elements/lesson-overview/lib/lesson-highlight.js b/elements/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-highlight.js similarity index 100% rename from elements/lesson-overview/lib/lesson-highlight.js rename to elements/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-highlight.js diff --git a/elements/lesson-overview/lib/lesson-overview.haxProperties.json b/elements/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-overview.haxProperties.json similarity index 100% rename from elements/lesson-overview/lib/lesson-overview.haxProperties.json rename to elements/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-overview.haxProperties.json diff --git a/elements/haxcms-elements/lib/ui-components/magic/site-ai-chat.js b/elements/haxcms-elements/lib/ui-components/magic/site-ai-chat.js index 6407097384..5e1ec80880 100644 --- a/elements/haxcms-elements/lib/ui-components/magic/site-ai-chat.js +++ b/elements/haxcms-elements/lib/ui-components/magic/site-ai-chat.js @@ -1,19 +1,14 @@ -import { DDDSuper, DDDPulseEffectSuper } from "@haxtheweb/d-d-d/d-d-d.js"; -import { html, css, LitElement } from "lit"; -import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; -import { toJS } from "mobx"; -import { MicroFrontendRegistry } from "@haxtheweb/micro-frontend-registry/micro-frontend-registry.js"; -import { enableServices } from "@haxtheweb/micro-frontend-registry/lib/microServices.js"; -import "@haxtheweb/simple-icon/lib/simple-icon-button-lite.js"; -import "@haxtheweb/chat-agent/chat-agent.js"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import { css, LitElement } from "lit"; -export class SiteAiChat extends DDDPulseEffectSuper(DDDSuper(LitElement)) { +export class SiteAiChat extends DDDSuper(LitElement) { static get tag() { return "site-ai-chat"; } constructor() { super(); + import("@haxtheweb/chat-agent/chat-agent.js"); } static get styles() { diff --git a/elements/haxcms-elements/lib/ui-components/navigation/site-menu.js b/elements/haxcms-elements/lib/ui-components/navigation/site-menu.js index 36f8d2c174..3357a12861 100644 --- a/elements/haxcms-elements/lib/ui-components/navigation/site-menu.js +++ b/elements/haxcms-elements/lib/ui-components/navigation/site-menu.js @@ -74,20 +74,6 @@ class SiteMenu extends HAXCMSThemeParts(LitElement) { var(--site-menu-scrollbar-thumb-color, #999999); background-color: var(--site-menu-scrollbar-thumb-color, #999999); } - .ops { - position: absolute; - display: block; - left: 0px; - height: 40px; - bottom: -40px; - z-index: 2; - margin: 0 4px 0 0; - } - .ops .op { - --simple-icon-height: 16px; - --simple-icon-width: 16px; - margin: 4px; - } `, ]; } @@ -132,16 +118,6 @@ class SiteMenu extends HAXCMSThemeParts(LitElement) { @active-item="${this.mapMenuActiveChanged}" @map-menu-operation-selected="${this.mapMenuOperationSelected}" > - ${this.editControls - ? html`
    - -
    ` - : ``} `; } @@ -174,7 +150,7 @@ class SiteMenu extends HAXCMSThemeParts(LitElement) { this.editControls = toJS(store.isLoggedIn); // dynamic import if we are logged in if (this.editControls) { - import("../../core/micros/haxcms-button-add.js"); + import("../../core/micros/haxcms-page-operations.js"); } } }); diff --git a/elements/haxcms-elements/lib/ui-components/utilities/site-available-themes.js b/elements/haxcms-elements/lib/ui-components/utilities/site-available-themes.js new file mode 100644 index 0000000000..c89176c2c0 --- /dev/null +++ b/elements/haxcms-elements/lib/ui-components/utilities/site-available-themes.js @@ -0,0 +1,624 @@ +/** + * Copyright 2025 The HAX team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LitElement, html, css } from "lit"; +import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; +import { I18NMixin } from "@haxtheweb/i18n-manager/lib/I18NMixin.js"; +import "@haxtheweb/media-image/media-image.js"; +import "@haxtheweb/simple-icon/simple-icon.js"; + +/** + * Site Available Themes + * Displays available HAXcms themes in a gallery or table format for documentation purposes + * Includes dynamic theme switching capabilities for live preview + * + * @element site-available-themes + */ +export class SiteAvailableThemes extends DDDSuper(I18NMixin(LitElement)) { + static get tag() { + return "site-available-themes"; + } + + constructor() { + super(); + this.themes = []; + this.loading = true; + this.error = null; + this.viewMode = "gallery"; // gallery or table + this.showDetails = true; + this.columns = 3; + this.currentTheme = ""; + this.t = this.t || {}; + + this.registerLocalization({ + context: this, + localesPath: + new URL( + "../../../../../../locales/site-available-themes/", + import.meta.url, + ).href + "/", + locales: ["en"], + }); + } + + static get properties() { + return { + ...super.properties, + themes: { type: Array }, + loading: { type: Boolean }, + error: { type: String }, + viewMode: { type: String, attribute: "view-mode" }, + showDetails: { type: Boolean, attribute: "show-details" }, + columns: { type: Number }, + currentTheme: { type: String, attribute: "current-theme" }, + }; + } + + static get styles() { + return [ + super.styles, + css` + :host { + display: block; + --theme-gallery-gap: var(--ddd-spacing-4); + --theme-gallery-border-radius: var(--ddd-radius-sm); + --theme-gallery-border: var(--ddd-border-sm); + } + + .loading, + .error { + text-align: center; + padding: var(--ddd-spacing-8); + color: var(--ddd-theme-default-coalyGray); + } + + .error { + color: var(--ddd-theme-default-original87Pink); + background: var(--ddd-theme-default-opportunityGreen); + border-radius: var(--theme-gallery-border-radius); + padding: var(--ddd-spacing-4); + } + + .gallery-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--ddd-spacing-4); + padding: var(--ddd-spacing-2) 0; + border-bottom: var(--theme-gallery-border); + } + + .gallery-title { + margin: 0; + color: var(--ddd-theme-default-coalyGray); + } + + .view-toggle { + display: flex; + gap: var(--ddd-spacing-2); + } + + .toggle-button { + background: var(--ddd-theme-default-white); + border: var(--theme-gallery-border); + border-radius: var(--theme-gallery-border-radius); + padding: var(--ddd-spacing-2) var(--ddd-spacing-4); + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: var(--ddd-spacing-1); + font-size: var(--ddd-font-size-xs); + } + + .toggle-button:hover, + .toggle-button.active { + background: var(--ddd-theme-default-skyBlue); + color: var(--ddd-theme-default-white); + } + + .theme-count { + color: var(--ddd-theme-default-coalyGray); + font-size: var(--ddd-font-size-xs); + } + + /* Gallery View */ + .gallery-grid { + display: grid; + grid-template-columns: repeat(var(--columns, 3), 1fr); + gap: var(--theme-gallery-gap); + } + + .theme-card { + border: var(--theme-gallery-border); + border-radius: var(--theme-gallery-border-radius); + overflow: hidden; + background: var(--ddd-theme-default-white); + transition: + transform 0.2s ease, + box-shadow 0.2s ease; + position: relative; + } + + .theme-card:hover { + transform: translateY(-2px); + box-shadow: var(--ddd-boxShadow-md); + } + + .theme-card.current { + border-color: var(--ddd-theme-default-skyBlue); + border-width: 2px; + } + + .theme-thumbnail { + width: 100%; + height: 200px; + position: relative; + } + + .current-badge { + position: absolute; + top: var(--ddd-spacing-2); + right: var(--ddd-spacing-2); + background: var(--ddd-theme-default-skyBlue); + color: var(--ddd-theme-default-white); + padding: var(--ddd-spacing-1) var(--ddd-spacing-2); + border-radius: var(--ddd-radius-xs); + font-size: var(--ddd-font-size-3xs); + z-index: 1; + } + + .theme-info { + padding: var(--ddd-spacing-4); + } + + .theme-name { + font-weight: var(--ddd-font-weight-bold); + color: var(--ddd-theme-default-coalyGray); + margin: 0 0 var(--ddd-spacing-2) 0; + font-size: var(--ddd-font-size-sm); + } + + .theme-description { + font-size: var(--ddd-font-size-xs); + color: var(--ddd-theme-default-slateGray); + margin: 0 0 var(--ddd-spacing-2) 0; + } + + .theme-element { + font-size: var(--ddd-font-size-3xs); + color: var(--ddd-theme-default-limestoneGray); + font-family: var(--ddd-font-navigation); + margin-bottom: var(--ddd-spacing-2); + } + + .preview-button { + background: var(--ddd-theme-default-skyBlue); + color: var(--ddd-theme-default-white); + border: none; + border-radius: var(--theme-gallery-border-radius); + padding: var(--ddd-spacing-2) var(--ddd-spacing-4); + cursor: pointer; + font-size: var(--ddd-font-size-xs); + display: flex; + align-items: center; + gap: var(--ddd-spacing-1); + transition: all 0.2s ease; + } + + .preview-button:hover { + background: var(--ddd-theme-default-navy); + } + + .preview-button:disabled { + background: var(--ddd-theme-default-limestoneGray); + cursor: not-allowed; + } + + /* Table View */ + .theme-table { + width: 100%; + border-collapse: collapse; + border: var(--theme-gallery-border); + border-radius: var(--theme-gallery-border-radius); + overflow: hidden; + } + + .theme-table th, + .theme-table td { + padding: var(--ddd-spacing-4); + text-align: left; + border-bottom: var(--theme-gallery-border); + } + + .theme-table th { + background: var(--ddd-theme-default-limestoneLight); + font-weight: var(--ddd-font-weight-bold); + color: var(--ddd-theme-default-coalyGray); + font-size: var(--ddd-font-size-xs); + } + + .theme-table tr:hover { + background: var(--ddd-theme-default-limestoneLight); + } + + .theme-table tr.current { + background: var(--ddd-theme-default-skyMaxlight); + } + + .table-thumbnail { + width: 80px; + height: 50px; + } + + .table-name { + font-weight: var(--ddd-font-weight-bold); + color: var(--ddd-theme-default-coalyGray); + } + + .table-element { + font-family: var(--ddd-font-navigation); + font-size: var(--ddd-font-size-xs); + color: var(--ddd-theme-default-limestoneGray); + } + + /* Responsive */ + @media (max-width: 768px) { + .gallery-grid { + grid-template-columns: repeat(2, 1fr); + } + + .gallery-header { + flex-direction: column; + gap: var(--ddd-spacing-2); + align-items: flex-start; + } + + .theme-table { + font-size: var(--ddd-font-size-xs); + } + + .table-thumbnail { + width: 60px; + height: 40px; + } + } + + @media (max-width: 480px) { + .gallery-grid { + grid-template-columns: 1fr; + } + } + `, + ]; + } + + updated(changedProperties) { + super.updated(changedProperties); + if (changedProperties.has("columns")) { + this.style.setProperty("--columns", this.columns); + } + } + + connectedCallback() { + super.connectedCallback(); + this.loadThemes(); + this.detectCurrentTheme(); + } + + async loadThemes() { + this.loading = true; + this.error = null; + + try { + // Load themes.json using relative path from component location + const themesUrl = new URL("../../themes.json", import.meta.url); + const response = await fetch(themesUrl); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const themesData = await response.json(); + + // Convert themes object to array and add screenshot paths + this.themes = Object.entries(themesData).map(([key, theme]) => ({ + key, + ...theme, + thumbnailPath: + theme.thumbnail || + `theme-screenshots/theme-${theme.element}-thumb.jpg`, + fullImagePath: + theme.screenshot || `theme-screenshots/theme-${theme.element}.jpg`, + })); + + this.loading = false; + } catch (error) { + this.error = `Failed to load themes: ${error.message}`; + this.loading = false; + console.error("Error loading themes:", error); + } + } + + detectCurrentTheme() { + // Try to detect the current theme from HAXCMS if available + if ( + globalThis.HAXCMS && + globalThis.HAXCMS.instance && + globalThis.HAXCMS.instance.store && + globalThis.HAXCMS.instance.store.manifest && + globalThis.HAXCMS.instance.store.manifest.metadata && + globalThis.HAXCMS.instance.store.manifest.metadata.theme && + globalThis.HAXCMS.instance.store.manifest.metadata.theme.element + ) { + this.currentTheme = + globalThis.HAXCMS.instance.store.manifest.metadata.theme.element; + } + } + + async switchTheme(themeElement) { + if (globalThis.HAXCMS && globalThis.HAXCMS.setTheme) { + try { + await globalThis.HAXCMS.setTheme(themeElement); + this.currentTheme = themeElement; + + // Fire a custom event to notify other components + this.dispatchEvent( + new CustomEvent("theme-changed", { + bubbles: true, + composed: true, + detail: { themeElement }, + }), + ); + + console.log(`Theme switched to: ${themeElement}`); + } catch (error) { + console.error("Failed to switch theme:", error); + } + } else { + console.warn("HAXCMS theme switching not available"); + } + } + + setViewMode(mode) { + this.viewMode = mode; + } + + renderGalleryView() { + return html` +
    + ${this.themes.map( + (theme) => html` +
    + ${theme.element === this.currentTheme + ? html`
    Current
    ` + : ""} +
    + +
    + ${this.showDetails + ? html` +
    +

    ${theme.name}

    + ${theme.description + ? html` +

    + ${theme.description} +

    + ` + : ""} +
    <${theme.element}>
    + +
    + ` + : ""} +
    + `, + )} +
    + `; + } + + renderTableView() { + return html` + + + + + + + ${this.showDetails ? html`` : ""} + + + + + ${this.themes.map( + (theme) => html` + + + + + ${this.showDetails + ? html`` + : ""} + + + `, + )} + +
    PreviewNameElementDescriptionAction
    +
    + +
    +
    +
    ${theme.name}
    + ${theme.element === this.currentTheme + ? html` +
    + Current +
    + ` + : ""} +
    +
    <${theme.element}>
    +
    ${theme.description || ""} + +
    + `; + } + + render() { + if (this.loading) { + return html`
    Loading themes...
    `; + } + + if (this.error) { + return html`
    ${this.error}
    `; + } + + return html` +
    +
    +

    Available HAXcms Themes

    +
    ${this.themes.length} themes available
    +
    +
    + + +
    +
    + + ${this.viewMode === "gallery" + ? this.renderGalleryView() + : this.renderTableView()} + `; + } + + /** + * HAX properties + */ + static get haxProperties() { + return { + canScale: false, + canPosition: false, + canEditSource: true, + contentEditable: false, + gizmo: { + title: "Available Themes", + description: + "Display available HAXcms themes with live preview switching", + icon: "image:collections", + color: "purple", + tags: ["Theme", "Gallery", "HAXcms", "Developer", "Documentation"], + handles: [], + meta: { + author: "HAXTheWeb team", + owner: "The Pennsylvania State University", + }, + }, + settings: { + configure: [ + { + property: "viewMode", + title: "View Mode", + description: "Choose how to display the themes", + inputMethod: "select", + options: { + gallery: "Gallery", + table: "Table", + }, + }, + { + property: "showDetails", + title: "Show Details", + description: "Show theme descriptions and additional information", + inputMethod: "boolean", + }, + { + property: "columns", + title: "Columns (Gallery)", + description: "Number of columns in gallery view", + inputMethod: "number", + min: 1, + max: 6, + }, + ], + }, + demoSchema: [ + { + tag: "site-available-themes", + properties: { + viewMode: "gallery", + showDetails: true, + columns: 3, + }, + content: "", + }, + ], + }; + } +} + +globalThis.customElements.define(SiteAvailableThemes.tag, SiteAvailableThemes); diff --git a/elements/haxcms-elements/package.json b/elements/haxcms-elements/package.json old mode 100755 new mode 100644 index 065b92050c..67a7d49e4c --- a/elements/haxcms-elements/package.json +++ b/elements/haxcms-elements/package.json @@ -44,14 +44,9 @@ "@github/time-elements": "3.1.4", "@haxtheweb/absolute-position-behavior": "^11.0.0", "@haxtheweb/anchor-behaviors": "^11.0.0", - "@haxtheweb/app-hax": "^11.0.5", "@haxtheweb/beaker-broker": "^11.0.5", - "@haxtheweb/chat-agent": "^11.0.5", "@haxtheweb/citation-element": "^11.0.5", - "@haxtheweb/clean-one": "^11.0.5", - "@haxtheweb/clean-two": "^11.0.5", "@haxtheweb/collection-list": "^11.0.5", - "@haxtheweb/course-design": "^11.0.5", "@haxtheweb/dynamic-import-registry": "^11.0.0", "@haxtheweb/editable-outline": "9.0.1", "@haxtheweb/editable-table": "^11.0.5", @@ -65,15 +60,12 @@ "@haxtheweb/html-block": "^11.0.0", "@haxtheweb/json-outline-schema": "^11.0.5", "@haxtheweb/jwt-login": "^11.0.5", - "@haxtheweb/learn-two-theme": "^11.0.5", - "@haxtheweb/lesson-overview": "^11.0.5", "@haxtheweb/license-element": "^11.0.5", "@haxtheweb/lrndesign-sidenote": "9.0.1", "@haxtheweb/lunr-search": "^11.0.0", "@haxtheweb/map-menu": "^11.0.5", "@haxtheweb/md-block": "^11.0.5", "@haxtheweb/outline-designer": "^11.0.5", - "@haxtheweb/outline-player": "^11.0.5", "@haxtheweb/page-contents-menu": "^11.0.0", "@haxtheweb/page-flag": "^11.0.0", "@haxtheweb/play-list": "^11.0.0", @@ -86,7 +78,6 @@ "@haxtheweb/runkit-embed": "^11.0.5", "@haxtheweb/schema-behaviors": "^11.0.5", "@haxtheweb/scroll-button": "^11.0.0", - "@haxtheweb/simple-blog": "^11.0.5", "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/simple-datetime": "^11.0.5", "@haxtheweb/simple-fields": "^11.0.0", @@ -101,18 +92,13 @@ "@haxtheweb/super-daemon": "^11.0.5", "@haxtheweb/video-player": "^11.0.5", "@haxtheweb/wc-autoload": "^11.0.0", - "@polymer/app-layout": "^3.0.2", "@polymer/iron-ajax": "3.0.1", - "@polymer/iron-list": "3.0.2", - "@polymer/paper-dialog": "3.0.1", - "@polymer/paper-dialog-scrollable": "^3.0.1", "@polymer/paper-input": "^3.0.2", - "@polymer/paper-item": "^3.0.1", - "@polymer/paper-toast": "3.0.1", + "@polymer/iron-list": "3.1.0", "@polymer/polymer": "3.5.2", "@vaadin/router": "^1.7.5", "@vaadin/vaadin-grid": "^5.2.5", - "lit": "3.3.0", + "lit": "3.3.1", "mobx": "6.13.7", "web-social-share": "8.0.1", "wired-elements": "3.0.0-rc.6" @@ -121,11 +107,8 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", "@web/dev-server-esbuild": "1.0.2", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "gulp-babel": "8.0.0", "wct-browser-legacy": "1.0.2" diff --git a/elements/haxcms-elements/theme-screenshot-automation.js b/elements/haxcms-elements/theme-screenshot-automation.js new file mode 100644 index 0000000000..2166990101 --- /dev/null +++ b/elements/haxcms-elements/theme-screenshot-automation.js @@ -0,0 +1,134 @@ +#!/usr/bin/env node + +/** + * HAX Theme Screenshot Automation + * + * This script automates the process of taking screenshots of all available HAX themes + * by using the HAXCMS.setTheme() method and Puppeteer via MCP integration. + */ + +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; +import { dirname } from "path"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +// Load themes configuration +const themesConfigPath = path.join(__dirname, "lib", "themes.json"); +const themesConfig = JSON.parse(fs.readFileSync(themesConfigPath, "utf8")); + +// Screenshot configuration +const SCREENSHOT_CONFIG = { + width: 1280, + height: 800, + baseUrl: "http://localhost:8000/elements/haxcms-elements/demo/", + outputDir: path.join(__dirname, "lib", "theme-screenshots"), + delay: 3000, // Wait time after theme change for rendering +}; + +// Ensure screenshot directory exists +if (!fs.existsSync(SCREENSHOT_CONFIG.outputDir)) { + fs.mkdirSync(SCREENSHOT_CONFIG.outputDir, { recursive: true }); +} + +/** + * Instructions for MCP Puppeteer automation + * + * This script provides the data and instructions needed to automate + * theme screenshots using the MCP Puppeteer tools in the AI agent. + */ + +class ThemeScreenshotAutomation { + static getThemesList() { + return Object.keys(themesConfig); + } + + static getThemeConfig(themeKey) { + return themesConfig[themeKey]; + } + + static getAllThemes() { + return themesConfig; + } + + static getScreenshotConfig() { + return SCREENSHOT_CONFIG; + } + + /** + * Generate the JavaScript code to execute in the browser for each theme + */ + static generateThemeSwitchCode(themeElement) { + return ` + // Switch to theme: ${themeElement} + console.log('Switching to theme: ${themeElement}'); + + if (typeof HAXCMS !== 'undefined' && HAXCMS.setTheme) { + HAXCMS.setTheme('${themeElement}'); + console.log('Theme set to: ${themeElement}'); + + // Wait for theme to load and render + setTimeout(() => { + console.log('Theme ${themeElement} should be fully loaded'); + }, ${SCREENSHOT_CONFIG.delay}); + + return 'Theme switch initiated for ${themeElement}'; + } else { + console.error('HAXCMS.setTheme not available'); + return 'Error: HAXCMS.setTheme not available'; + } + `; + } + + /** + * Update themes.json with new screenshot timestamp + */ + static updateThemeScreenshot(themeKey) { + if (themesConfig[themeKey]) { + themesConfig[themeKey].screenshotGenerated = new Date().toISOString(); + fs.writeFileSync(themesConfigPath, JSON.stringify(themesConfig, null, 2)); + console.log(`Updated screenshot timestamp for ${themeKey}`); + } + } + + /** + * Generate automation instructions for the AI agent + */ + static getAutomationInstructions() { + const themes = this.getThemesList(); + + return { + totalThemes: themes.length, + themes: themes, + config: SCREENSHOT_CONFIG, + instructions: ` + Automation Instructions for MCP Puppeteer: + + 1. Navigate to: ${SCREENSHOT_CONFIG.baseUrl} + 2. Wait for HAXCMS to load completely + 3. For each theme in the themes array: + a. Execute theme switch JavaScript code + b. Wait ${SCREENSHOT_CONFIG.delay}ms for rendering + c. Take screenshot at ${SCREENSHOT_CONFIG.width}x${SCREENSHOT_CONFIG.height} + d. Save as theme-screenshots/{theme-element}.png + + Themes to process: ${themes.length} total + ${themes.map((theme, i) => `${i + 1}. ${theme}`).join("\n ")} + `, + }; + } +} + +// Export for use in automation +if (import.meta.url === `file://${process.argv[1]}`) { + // Command line usage + const instructions = ThemeScreenshotAutomation.getAutomationInstructions(); + console.log(instructions.instructions); + console.log("\nTheme list:", instructions.themes); + console.log("\nTotal themes to process:", instructions.totalThemes); +} + +// ES module export +export default ThemeScreenshotAutomation; diff --git a/elements/haxor-slevin/.gitignore b/elements/haxor-slevin/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/haxor-slevin/.gitignore +++ b/elements/haxor-slevin/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/haxor-slevin/custom-elements.json b/elements/haxor-slevin/custom-elements.json old mode 100755 new mode 100644 index 71ed90475f..c69adb189a --- a/elements/haxor-slevin/custom-elements.json +++ b/elements/haxor-slevin/custom-elements.json @@ -8,7 +8,7 @@ "declarations": [ { "kind": "class", - "description": "`haxor-slevin`\n`Tech blogger theme`", + "description": "`Haxor blog`\n`A theme for blogs and personal sites`", "name": "HaxorSlevin", "members": [ { diff --git a/elements/haxor-slevin/demo/index.html b/elements/haxor-slevin/demo/index.html index a348e0d804..b1c080b814 100644 --- a/elements/haxor-slevin/demo/index.html +++ b/elements/haxor-slevin/demo/index.html @@ -9,10 +9,10 @@ - +
    diff --git a/elements/haxor-slevin/haxor-slevin.js b/elements/haxor-slevin/haxor-slevin.js index e970f12680..bf41ac96da 100644 --- a/elements/haxor-slevin/haxor-slevin.js +++ b/elements/haxor-slevin/haxor-slevin.js @@ -4,7 +4,6 @@ */ import { html, css } from "lit"; import { HAXCMSLitElementTheme } from "@haxtheweb/haxcms-elements/lib/core/HAXCMSLitElementTheme.js"; -import { HAXCMSRememberRoute } from "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSRememberRoute.js"; import { HAXCMSThemeParts } from "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSThemeParts.js"; import { SimpleColorsSuper } from "@haxtheweb/simple-colors/simple-colors.js"; import { store } from "@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js"; @@ -730,13 +729,8 @@ class HaxorSlevin extends HAXCMSThemeParts( top: 0, left: 0, }); - const evt = new CustomEvent("json-outline-schema-active-item-changed", { - bubbles: true, - cancelable: true, - composed: true, - detail: {}, - }); - this.dispatchEvent(evt); + // Note: json-outline-schema-active-item-changed is automatically dispatched + // by the store's autorun when activeId changes via the router/popstate flow this.selectedPage = 0; } } diff --git a/elements/haxor-slevin/index.html b/elements/haxor-slevin/index.html index dd54d35c26..20f12f194b 100755 --- a/elements/haxor-slevin/index.html +++ b/elements/haxor-slevin/index.html @@ -4,10 +4,10 @@ haxor-slevin documentation - - + + - + diff --git a/elements/haxor-slevin/package.json b/elements/haxor-slevin/package.json old mode 100755 new mode 100644 index 5c3e13e454..80679fc3cd --- a/elements/haxor-slevin/package.json +++ b/elements/haxor-slevin/package.json @@ -2,7 +2,7 @@ "name": "@haxtheweb/haxor-slevin", "wcfactory": { "className": "HaxorSlevin", - "customElementClass": "PolymerElement", + "customElementClass": "LitElement", "elementName": "haxor-slevin", "generator-wcfactory-version": "0.7.3", "useHAX": false, @@ -47,19 +47,13 @@ "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/social-share-link": "^11.0.0", - "@polymer/app-layout": "^3.0.2", - "@polymer/iron-list": "3.0.2", - "@polymer/iron-pages": "3.0.1", - "@polymer/polymer": "3.5.2" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/hex-picker/.gitignore b/elements/hex-picker/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/hex-picker/.gitignore +++ b/elements/hex-picker/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/hex-picker/custom-elements.json b/elements/hex-picker/custom-elements.json deleted file mode 100644 index d276c59248..0000000000 --- a/elements/hex-picker/custom-elements.json +++ /dev/null @@ -1,223 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "hex-picker.js", - "declarations": [ - { - "kind": "class", - "description": "`hex-picker`\n`Choose a color by hex or rgba code`", - "name": "HexPicker", - "members": [ - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Convention we use", - "readonly": true - }, - { - "kind": "method", - "name": "_validateInput", - "parameters": [ - { - "name": "event" - } - ] - }, - { - "kind": "method", - "name": "_padHex", - "parameters": [ - { - "name": "n" - } - ] - }, - { - "kind": "method", - "name": "_computeHex" - }, - { - "kind": "method", - "name": "_inputChanged", - "parameters": [ - { - "name": "event" - } - ] - }, - { - "kind": "method", - "name": "_updateSliders", - "parameters": [ - { - "name": "rgb" - } - ] - }, - { - "kind": "method", - "name": "_hexToRgb", - "parameters": [ - { - "name": "hex" - } - ] - }, - { - "kind": "method", - "name": "_fieldSetChange", - "parameters": [ - { - "name": "event" - } - ] - }, - { - "kind": "method", - "name": "_dispatchChange" - }, - { - "kind": "method", - "name": "renderFieldSet", - "parameters": [ - { - "name": "value" - } - ] - }, - { - "kind": "field", - "name": "value", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"#000000FF\"", - "attribute": "value", - "reflects": true - }, - { - "kind": "field", - "name": "_rValue", - "type": { - "text": "number" - }, - "default": "0" - }, - { - "kind": "field", - "name": "_gValue", - "type": { - "text": "number" - }, - "default": "0" - }, - { - "kind": "field", - "name": "_bValue", - "type": { - "text": "number" - }, - "default": "0" - }, - { - "kind": "field", - "name": "_oValue", - "type": { - "text": "number" - }, - "default": "255" - }, - { - "kind": "field", - "name": "disabled", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "disabled", - "reflects": true - }, - { - "kind": "field", - "name": "largeDisplay", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "large-display", - "reflects": true - } - ], - "events": [ - { - "name": "value-changed", - "type": { - "text": "CustomEvent" - } - } - ], - "attributes": [ - { - "name": "value", - "type": { - "text": "string" - }, - "default": "\"#000000FF\"", - "fieldName": "value" - }, - { - "name": "disabled", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "disabled" - }, - { - "name": "large-display", - "type": { - "text": "boolean" - }, - "fieldName": "largeDisplay" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "hex-picker", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "HexPicker", - "module": "hex-picker.js" - } - }, - { - "kind": "js", - "name": "HexPicker", - "declaration": { - "name": "HexPicker", - "module": "hex-picker.js" - } - } - ] - } - ] -} diff --git a/elements/hex-picker/demo/index.html b/elements/hex-picker/demo/index.html index 282fe3567f..69010eb037 100644 --- a/elements/hex-picker/demo/index.html +++ b/elements/hex-picker/demo/index.html @@ -4,15 +4,14 @@ HexPicker: hex-picker Demo - - +
    diff --git a/elements/hex-picker/index.html b/elements/hex-picker/index.html index 11058e5327..0465882d85 100644 --- a/elements/hex-picker/index.html +++ b/elements/hex-picker/index.html @@ -4,10 +4,10 @@ hex-picker documentation - - + + - + diff --git a/elements/hex-picker/package.json b/elements/hex-picker/package.json index 7da10f4f18..1095fa2c47 100644 --- a/elements/hex-picker/package.json +++ b/elements/hex-picker/package.json @@ -44,16 +44,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/hexagon-loader/.gitignore b/elements/hexagon-loader/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/hexagon-loader/.gitignore +++ b/elements/hexagon-loader/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/hexagon-loader/custom-elements.json b/elements/hexagon-loader/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/hexagon-loader/demo/hexagon.html b/elements/hexagon-loader/demo/hexagon.html index 1df7edc4af..b9ad5b36e8 100644 --- a/elements/hexagon-loader/demo/hexagon.html +++ b/elements/hexagon-loader/demo/hexagon.html @@ -9,7 +9,7 @@ +
    diff --git a/elements/html-block/index.html b/elements/html-block/index.html index 2905dd9ee6..cf4ea3dcb0 100755 --- a/elements/html-block/index.html +++ b/elements/html-block/index.html @@ -4,10 +4,10 @@ html-block documentation - - + + - + diff --git a/elements/html-block/package.json b/elements/html-block/package.json old mode 100755 new mode 100644 index d61a2ae60d..36b6fed4f1 --- a/elements/html-block/package.json +++ b/elements/html-block/package.json @@ -40,10 +40,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/i18n-manager/.gitignore b/elements/i18n-manager/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/i18n-manager/.gitignore +++ b/elements/i18n-manager/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/i18n-manager/custom-elements.json b/elements/i18n-manager/custom-elements.json index 9df26317ff..1008f87816 100644 --- a/elements/i18n-manager/custom-elements.json +++ b/elements/i18n-manager/custom-elements.json @@ -67,6 +67,34 @@ ], "description": "Register a localization with the manager" }, + { + "kind": "method", + "name": "loadTranslationManifest", + "description": "Lazy load the translation manifest only when needed (non-English language)" + }, + { + "kind": "method", + "name": "hasTranslation", + "parameters": [ + { + "name": "namespace" + }, + { + "name": "language" + } + ], + "description": "Check if a namespace supports a specific language\nReturns false if manifest is not loaded and language needs manifest" + }, + { + "kind": "method", + "name": "needsManifest", + "parameters": [ + { + "name": "language" + } + ], + "description": "Determine if we need to load the manifest for this language" + }, { "kind": "field", "name": "tag", @@ -131,6 +159,30 @@ "kind": "field", "name": "locales", "default": "new Set([])" + }, + { + "kind": "field", + "name": "translationManifest", + "type": { + "text": "null" + }, + "default": "null" + }, + { + "kind": "field", + "name": "manifestLoaded", + "type": { + "text": "boolean" + }, + "default": "false" + }, + { + "kind": "field", + "name": "manifestLoading", + "type": { + "text": "boolean" + }, + "default": "false" } ], "events": [ diff --git a/elements/i18n-manager/demo/index.html b/elements/i18n-manager/demo/index.html index 6c69e5b450..87b16b9c77 100644 --- a/elements/i18n-manager/demo/index.html +++ b/elements/i18n-manager/demo/index.html @@ -4,18 +4,17 @@ I18NManager: i18n-manager Demo - - +
    diff --git a/elements/i18n-manager/i18n-manager.js b/elements/i18n-manager/i18n-manager.js index bb4c732308..db192b764f 100644 --- a/elements/i18n-manager/i18n-manager.js +++ b/elements/i18n-manager/i18n-manager.js @@ -113,7 +113,10 @@ class I18NManager extends LitElement { this.dir = nextDir; } }); - const opts = { attributes: true, attributeFilter: ["lang", "xml:lang", "dir", "xml:dir"] }; + const opts = { + attributes: true, + attributeFilter: ["lang", "xml:lang", "dir", "xml:dir"], + }; if (globalThis.document && globalThis.document.documentElement) { this._docObserver.observe(globalThis.document.documentElement, opts); } @@ -219,7 +222,7 @@ class I18NManager extends LitElement { ) { detail = this.detailNormalize(detail); this.elements.push(detail); - + // store in this.locales for quick "do we support this" look up if (detail && detail.locales) { detail.locales.forEach(this.locales.add, this.locales); @@ -231,7 +234,9 @@ class I18NManager extends LitElement { this.lang && this.__ready && ((detail.locales && detail.locales.includes(this.lang)) || - (!detail.locales && this.lang !== 'en' && !this.lang.startsWith('en-'))) + (!detail.locales && + this.lang !== "en" && + !this.lang.startsWith("en-"))) ) { // prevent flooding w/ lots of translatable elements clearTimeout(this._debounce); @@ -248,22 +253,27 @@ class I18NManager extends LitElement { if (this.manifestLoaded || this.manifestLoading) { return this.translationManifest; } - + this.manifestLoading = true; try { - const manifestUrl = new URL('./lib/translation-manifest.json', import.meta.url).href; + const manifestUrl = new URL( + "./lib/translation-manifest.json", + import.meta.url, + ).href; const response = await fetch(manifestUrl); if (response.ok) { const data = await response.json(); this.translationManifest = data.manifest || {}; this.manifestLoaded = true; } else { - console.warn('Translation manifest not found, falling back to component locales'); + console.warn( + "Translation manifest not found, falling back to component locales", + ); this.translationManifest = {}; this.manifestLoaded = true; } } catch (e) { - console.warn('Failed to load translation manifest:', e.message); + console.warn("Failed to load translation manifest:", e.message); this.translationManifest = {}; this.manifestLoaded = true; } finally { @@ -280,14 +290,17 @@ class I18NManager extends LitElement { // If manifest isn't loaded but we need it, return false to let component locales handle it return false; } - return this.translationManifest[namespace] && this.translationManifest[namespace].includes(language); + return ( + this.translationManifest[namespace] && + this.translationManifest[namespace].includes(language) + ); } /** * Determine if we need to load the manifest for this language */ needsManifest(language) { // Only load manifest for non-English languages - return language && language !== 'en' && !language.startsWith('en-'); + return language && language !== "en" && !language.startsWith("en-"); } /** * Convention we use @@ -306,33 +319,38 @@ class I18NManager extends LitElement { // Use the first match for namespace info, multiple elements can share the same namespace if (nsMatch && nsMatch.length >= 1) { const el = nsMatch[0]; - + // Load manifest if needed (only for non-English) if (this.needsManifest(lang) && !this.manifestLoaded) { await this.loadTranslationManifest(); } - + // Check manifest first to avoid unnecessary requests const supportsExact = this.hasTranslation(ns, lang); const supportsBase = this.hasTranslation(ns, langPieces[0]); - + // Fallback to component locales if manifest not loaded or doesn't contain info const componentSupportsExact = el.locales && el.locales.includes(lang); - const componentSupportsBase = el.locales && el.locales.includes(langPieces[0]); - + const componentSupportsBase = + el.locales && el.locales.includes(langPieces[0]); + // If we have manifest data, use it; otherwise fallback to component data - const shouldLoadExact = supportsExact || (!this.manifestLoaded && componentSupportsExact); - const shouldLoadBase = supportsBase || (!this.manifestLoaded && componentSupportsBase); - + const shouldLoadExact = + supportsExact || (!this.manifestLoaded && componentSupportsExact); + const shouldLoadBase = + supportsBase || (!this.manifestLoaded && componentSupportsBase); + // For manifest-based elements (no locales), try to load if manifest is loaded and supports it // or if manifest isn't loaded yet, attempt to load anyway (will 404 if not available) const isManifestBased = !el.locales; - const shouldAttemptManifestLoad = isManifestBased && (supportsExact || supportsBase || !this.manifestLoaded); - + const shouldAttemptManifestLoad = + isManifestBased && + (supportsExact || supportsBase || !this.manifestLoaded); + if (!shouldLoadExact && !shouldLoadBase && !shouldAttemptManifestLoad) { return {}; } - + var fetchTarget = ""; if (shouldLoadExact || (isManifestBased && supportsExact)) { fetchTarget = `${el.localesPath}/${el.namespace}.${lang}.json`; @@ -342,11 +360,11 @@ class I18NManager extends LitElement { // For manifest-based elements, try exact match first when manifest not loaded yet fetchTarget = `${el.localesPath}/${el.namespace}.${lang}.json`; } - + if (fetchTarget == "") { return {}; } - + // see if we had this previous to avoid another request if (!this.fetchTargets[fetchTarget]) { this.fetchTargets[fetchTarget] = await fetch(fetchTarget).then( @@ -365,7 +383,7 @@ class I18NManager extends LitElement { async updateLanguage(lang) { if (lang) { const langPieces = lang.split("-"); - + // Load manifest if needed (only for non-English) if (this.needsManifest(lang) && !this.manifestLoaded) { await this.loadTranslationManifest(); @@ -377,17 +395,26 @@ class I18NManager extends LitElement { const supportsExact = this.hasTranslation(el.namespace, lang); const supportsBase = this.hasTranslation(el.namespace, langPieces[0]); // Fallback to component locales if not in manifest - const componentSupportsExact = el.locales && el.locales.includes(lang); - const componentSupportsBase = el.locales && el.locales.includes(langPieces[0]); - + const componentSupportsExact = + el.locales && el.locales.includes(lang); + const componentSupportsBase = + el.locales && el.locales.includes(langPieces[0]); + // For elements without locales, include them if: // 1. The manifest is loaded and supports the language, OR // 2. The manifest isn't loaded yet but the language needs manifest (let's try to load it) - const manifestBasedSupport = !el.locales && + const manifestBasedSupport = + !el.locales && ((this.manifestLoaded && (supportsExact || supportsBase)) || - (!this.manifestLoaded && this.needsManifest(lang))); - - return supportsExact || supportsBase || componentSupportsExact || componentSupportsBase || manifestBasedSupport; + (!this.manifestLoaded && this.needsManifest(lang))); + + return ( + supportsExact || + supportsBase || + componentSupportsExact || + componentSupportsBase || + manifestBasedSupport + ); } catch (e) { console.error("i18n registration incorrect in:", el, e); } @@ -396,15 +423,24 @@ class I18NManager extends LitElement { try { const supportsExact = this.hasTranslation(el.namespace, lang); const supportsBase = this.hasTranslation(el.namespace, langPieces[0]); - const componentSupportsExact = el.locales && el.locales.includes(lang); - const componentSupportsBase = el.locales && el.locales.includes(langPieces[0]); - + const componentSupportsExact = + el.locales && el.locales.includes(lang); + const componentSupportsBase = + el.locales && el.locales.includes(langPieces[0]); + // For elements without locales, check if they're supported by manifest - const manifestBasedSupport = !el.locales && + const manifestBasedSupport = + !el.locales && ((this.manifestLoaded && (supportsExact || supportsBase)) || - (!this.manifestLoaded && this.needsManifest(lang))); - - return !supportsExact && !supportsBase && !componentSupportsExact && !componentSupportsBase && !manifestBasedSupport; + (!this.manifestLoaded && this.needsManifest(lang))); + + return ( + !supportsExact && + !supportsBase && + !componentSupportsExact && + !componentSupportsBase && + !manifestBasedSupport + ); } catch (e) { return true; // Fallback on error } @@ -433,12 +469,21 @@ class I18NManager extends LitElement { const supportsExact = this.hasTranslation(el.namespace, lang); const supportsBase = this.hasTranslation(el.namespace, langPieces[0]); const componentSupportsExact = el.locales && el.locales.includes(lang); - const componentSupportsBase = el.locales && el.locales.includes(langPieces[0]); + const componentSupportsBase = + el.locales && el.locales.includes(langPieces[0]); const isManifestBased = !el.locales; - - if (supportsExact || componentSupportsExact || (isManifestBased && supportsExact)) { + + if ( + supportsExact || + componentSupportsExact || + (isManifestBased && supportsExact) + ) { fetchTarget = `${el.localesPath}/${el.namespace}.${lang}.json`; - } else if (supportsBase || componentSupportsBase || (isManifestBased && supportsBase)) { + } else if ( + supportsBase || + componentSupportsBase || + (isManifestBased && supportsBase) + ) { fetchTarget = `${el.localesPath}/${el.namespace}.${langPieces[0]}.json`; } else if (isManifestBased && !this.manifestLoaded) { // For manifest-based elements, try exact match first when manifest not loaded yet diff --git a/elements/i18n-manager/index.html b/elements/i18n-manager/index.html index 6113e5404c..f2034fd53b 100644 --- a/elements/i18n-manager/index.html +++ b/elements/i18n-manager/index.html @@ -4,10 +4,10 @@ i18n-manager documentation - - + + - + diff --git a/elements/i18n-manager/lib/translation-manifest.json b/elements/i18n-manager/lib/translation-manifest.json index 3e3daf3bd5..22c12dd73a 100644 --- a/elements/i18n-manager/lib/translation-manifest.json +++ b/elements/i18n-manager/lib/translation-manifest.json @@ -1,9 +1,9 @@ { "_meta": { - "generated": "2025-10-01T17:03:27.741Z", - "elementsScanned": 221, - "elementsWithTranslations": 32, - "translationFilesFound": 697, + "generated": "2026-01-09T17:34:56.431Z", + "elementsScanned": 219, + "elementsWithTranslations": 33, + "translationFilesFound": 715, "languagesSupported": [ "af", "am", @@ -299,6 +299,44 @@ "ddd-card": [ "es" ], + "demo-snippet.ar": [ + "haxProperties" + ], + "demo-snippet": [ + "ar", + "bn", + "es", + "fr", + "hi", + "ja", + "pt", + "ru", + "zh" + ], + "demo-snippet.bn": [ + "haxProperties" + ], + "demo-snippet.es": [ + "haxProperties" + ], + "demo-snippet.fr": [ + "haxProperties" + ], + "demo-snippet.hi": [ + "haxProperties" + ], + "demo-snippet.ja": [ + "haxProperties" + ], + "demo-snippet.pt": [ + "haxProperties" + ], + "demo-snippet.ru": [ + "haxProperties" + ], + "demo-snippet.zh": [ + "haxProperties" + ], "example-hax-element.ar": [ "haxProperties" ], diff --git a/elements/i18n-manager/package.json b/elements/i18n-manager/package.json index 2d98f58aec..c6dd836d4b 100644 --- a/elements/i18n-manager/package.json +++ b/elements/i18n-manager/package.json @@ -43,10 +43,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/iframe-loader/.gitignore b/elements/iframe-loader/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/iframe-loader/.gitignore +++ b/elements/iframe-loader/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/iframe-loader/demo/index.html b/elements/iframe-loader/demo/index.html index 43c9dc4240..7585c6ccb6 100644 --- a/elements/iframe-loader/demo/index.html +++ b/elements/iframe-loader/demo/index.html @@ -8,10 +8,10 @@ - +
    diff --git a/elements/iframe-loader/index.html b/elements/iframe-loader/index.html index 77b17d8197..ab030134d9 100644 --- a/elements/iframe-loader/index.html +++ b/elements/iframe-loader/index.html @@ -4,10 +4,10 @@ iframe-loader documentation - - + + - + diff --git a/elements/iframe-loader/package.json b/elements/iframe-loader/package.json index 7f71c2a729..3309a97a2f 100644 --- a/elements/iframe-loader/package.json +++ b/elements/iframe-loader/package.json @@ -37,15 +37,12 @@ "license": "Apache-2.0", "dependencies": { "@polymer/polymer": "3.5.2", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/image-compare-slider/.gitignore b/elements/image-compare-slider/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/image-compare-slider/.gitignore +++ b/elements/image-compare-slider/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/image-compare-slider/custom-elements.json b/elements/image-compare-slider/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/image-compare-slider/demo/index.html b/elements/image-compare-slider/demo/index.html index 516a611c0b..7faa4f55a5 100644 --- a/elements/image-compare-slider/demo/index.html +++ b/elements/image-compare-slider/demo/index.html @@ -7,7 +7,7 @@ +
    diff --git a/elements/image-inspector/image-inspector.js b/elements/image-inspector/image-inspector.js index eb30597096..7c186fb482 100644 --- a/elements/image-inspector/image-inspector.js +++ b/elements/image-inspector/image-inspector.js @@ -1,7 +1,7 @@ import { LitElement, html, css } from "lit"; import "@haxtheweb/simple-icon/simple-icon.js"; import "@haxtheweb/simple-icon/lib/simple-icons.js"; -import "@haxtheweb/simple-icon/lib/simple-icon-button.js"; +import "@haxtheweb/simple-icon/lib/simple-icon-button-lite.js"; import "@haxtheweb/img-pan-zoom/img-pan-zoom.js"; /** * `image-inspector` @@ -21,27 +21,29 @@ class ImageInspector extends LitElement { overflow: hidden; } - simple-icon-button { + simple-icon-button-lite { display: inline-flex; - --simple-icon-width: 36px; - --simple-icon-height: 36px; - margin: 0 4px; - padding: 0 4px; - border-radius: 0; - background-color: var(--image-inspector-background-color, #fdfdfd); - transition: 0.3s all ease-in-out; + --simple-icon-width: var(--ddd-icon-xs, 32px); + --simple-icon-height: var(--ddd-icon-xs, 32px); + margin: 0 var(--ddd-spacing-1, 4px); + padding: var(--ddd-spacing-1, 4px); + border-radius: var(--ddd-radius-0, 0); + background-color: var(--image-inspector-background-color, light-dark(var(--ddd-theme-default-white, #fdfdfd), var(--ddd-theme-default-coalyGray, #262626))); + color: var(--image-inspector-color, light-dark(var(--ddd-theme-default-coalyGray, #262626), var(--ddd-theme-default-white, #ffffff))); + transition: var(--ddd-duration-normal, 0.3s) all var(--ddd-timing-ease, ease-in-out); + cursor: pointer; } - simple-icon-button:hover, - simple-icon-button:focus, - simple-icon-button:active { + simple-icon-button-lite:hover, + simple-icon-button-lite:focus, + simple-icon-button-lite:active { background-color: var( --image-inspector-background-color-active, - #dddddd + light-dark(var(--ddd-theme-default-limestoneLight, #e4e5e7), var(--ddd-theme-default-slateGray, #314d64)) ); } img-pan-zoom.top-rotated { - top: 150px; + top: var(--ddd-spacing-30, 150px); pointer-events: none; /** disable pointer events when rotated bc of HTML canvas issue */ height: var(--image-inspector-height-rotated, 600px); } @@ -53,8 +55,8 @@ class ImageInspector extends LitElement { display: flex; } .internal-btn-wrap { - border: 2px solid black; - background-color: var(--image-inspector-background-color, #fdfdfd); + border: var(--ddd-border-md, 2px) solid var(--image-inspector-border-color, light-dark(var(--ddd-theme-default-coalyGray, #262626), var(--ddd-theme-default-white, #ffffff))); + background-color: var(--image-inspector-background-color, light-dark(var(--ddd-theme-default-white, #fdfdfd), var(--ddd-theme-default-coalyGray, #262626))); } `, ]; @@ -69,36 +71,36 @@ class ImageInspector extends LitElement { return html`
    - - + - + - + + > - + >
    diff --git a/elements/image-inspector/index.html b/elements/image-inspector/index.html index 56f8a91a2f..88867c269b 100755 --- a/elements/image-inspector/index.html +++ b/elements/image-inspector/index.html @@ -4,10 +4,10 @@ image-inspector documentation - - + + - + diff --git a/elements/image-inspector/package.json b/elements/image-inspector/package.json old mode 100755 new mode 100644 index 0a6e98e25b..26a3f5ce9c --- a/elements/image-inspector/package.json +++ b/elements/image-inspector/package.json @@ -43,16 +43,13 @@ "dependencies": { "@haxtheweb/img-pan-zoom": "^11.0.0", "@haxtheweb/simple-icon": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/img-pan-zoom/.gitignore b/elements/img-pan-zoom/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/img-pan-zoom/.gitignore +++ b/elements/img-pan-zoom/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/img-pan-zoom/custom-elements.json b/elements/img-pan-zoom/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/img-pan-zoom/demo/index.html b/elements/img-pan-zoom/demo/index.html index fc12fbc267..ac6d9e1c74 100644 --- a/elements/img-pan-zoom/demo/index.html +++ b/elements/img-pan-zoom/demo/index.html @@ -7,10 +7,10 @@ - +
    diff --git a/elements/img-pan-zoom/img-pan-zoom.js b/elements/img-pan-zoom/img-pan-zoom.js index 9785e7e142..02497bf208 100644 --- a/elements/img-pan-zoom/img-pan-zoom.js +++ b/elements/img-pan-zoom/img-pan-zoom.js @@ -540,7 +540,6 @@ class ImgPanZoom extends LitElement { showRotationControl: this.showRotationControl, minZoomImageRatio: this.minZoomImageRatio, maxZoomPixelRatio: this.maxZoomPixelRatio, - showNavigationControl: this.showNavigationControl, navigatorAutoFade: this.navigatorAutoFade, navigatorPosition: this.navigatorPosition, navigatorLeft: this.navigatorLeft, diff --git a/elements/img-pan-zoom/index.html b/elements/img-pan-zoom/index.html index fbe2ae2fcc..84641bfb03 100755 --- a/elements/img-pan-zoom/index.html +++ b/elements/img-pan-zoom/index.html @@ -4,10 +4,10 @@ img-pan-zoom documentation - - + + - + diff --git a/elements/img-pan-zoom/package.json b/elements/img-pan-zoom/package.json old mode 100755 new mode 100644 index d1d17bd30c..094282cc52 --- a/elements/img-pan-zoom/package.json +++ b/elements/img-pan-zoom/package.json @@ -2,7 +2,7 @@ "name": "@haxtheweb/img-pan-zoom", "wcfactory": { "className": "ImgPanZoom", - "customElementClass": "PolymerElement", + "customElementClass": "LitElement", "elementName": "img-pan-zoom", "generator-wcfactory-version": "0.3.2", "useHAX": true, @@ -43,17 +43,13 @@ "dependencies": { "@haxtheweb/es-global-bridge": "^11.0.0", "@haxtheweb/hexagon-loader": "^11.0.5", - "@polymer/paper-spinner": "3.0.2", - "@polymer/polymer": "3.5.2" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/img-view-modal/.gitignore b/elements/img-view-modal/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/img-view-modal/.gitignore +++ b/elements/img-view-modal/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/img-view-modal/custom-elements.json b/elements/img-view-modal/custom-elements.json deleted file mode 100755 index eeaac92ca7..0000000000 --- a/elements/img-view-modal/custom-elements.json +++ /dev/null @@ -1,795 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "img-view-modal.js", - "declarations": [ - { - "kind": "class", - "description": "`img-view-modal`\nCombines img-pan-zoom and simple-modal for an easy image zoom solution\n\n### Styling\n\nCustom property | Description | Default\n----------------|-------------|----------\n`--img-view-modal-width` | sets width of modal | 90%\n`--img-view-modal-height` | sets height of modal | 90vh\n`--img-view-modal-backgroundColor` | background color | white\n`--img-view-modal-color` | text color | black\n`--img-view-modal-borderColor` | border color | #ddd\n`--img-view-modal-toggled-backgroundColor` | background color of toggled buttons and kbd commands | #eee", - "name": "ImgViewModal", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true - }, - { - "kind": "method", - "name": "_getCssVar", - "parameters": [ - { - "name": "propName" - } - ] - }, - { - "kind": "method", - "name": "modalOpen" - }, - { - "kind": "field", - "name": "modal", - "privacy": "public", - "type": { - "text": "object" - }, - "attribute": "modal" - }, - { - "kind": "field", - "name": "title", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "title" - } - ], - "events": [ - { - "name": "modal-button-click", - "type": { - "text": "CustomEvent" - } - } - ], - "attributes": [ - { - "name": "title", - "type": { - "text": "string" - }, - "fieldName": "title" - }, - { - "name": "modal", - "type": { - "text": "object" - }, - "fieldName": "modal" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "img-view-modal", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "ImgViewModal", - "module": "img-view-modal.js" - } - }, - { - "kind": "js", - "name": "ImgViewModal", - "declaration": { - "name": "ImgViewModal", - "module": "img-view-modal.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/img-view-viewer.js", - "declarations": [ - { - "kind": "class", - "description": "`img-view-viewer`\nCombines img-pan-zoom and simple-modal for an easy image zoom solution\n\n### Styling\n\nCustom property | Description | Default\n----------------|-------------|----------\n`--img-view-viewer-height` | viewer height | 500px\n`--img-view-viewer-backgroundColor` | background color | white\n`--img-view-viewer-color` | text color | black\n`--img-view-viewer-borderColor` | border color | #ddd\n`--img-view-viewer-toggled-backgroundColor` | background color of toggled buttons and kbd commands | #eee", - "name": "ImgViewViewer", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "toolbarsHeight", - "readonly": true - }, - { - "kind": "method", - "name": "getToolbars", - "parameters": [ - { - "name": "topOrBottom", - "default": "\"bottom\"" - } - ] - }, - { - "kind": "field", - "name": "homebutton", - "description": "default home button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "fullscreenbutton", - "description": "default toggle fullscreen button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "fullscreenTarget", - "description": "element to make fullscreen, can be overidden", - "readonly": true - }, - { - "kind": "field", - "name": "navigatorbutton", - "description": "default toggle navigate window button configuration\nUses Viewport Navigator", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "infobutton", - "description": "default toggle info button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "kbdbutton", - "description": "default toggle keyboard shorcuts help button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "flipbutton", - "description": "default flip horizontal button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "rotategroup", - "description": "default rotate button group configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "rotateccwbutton", - "description": "default rotate counterclockwise button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "rotatecwbutton", - "description": "default rotate counter button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "pangroup", - "description": "default pan button group configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "panleftbutton", - "description": "default pan left button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "panupbutton", - "description": "default pan up button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "pandownbutton", - "description": "default pan down button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "panrightbutton", - "description": "default pan right button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "zoomgroup", - "description": "default zoom button group configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "zoominbutton", - "description": "default zoom in button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "zoomoutbutton", - "description": "default zoom out button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "prevbutton", - "description": "default prev button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "nextbutton", - "description": "default next button configuration", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "pages", - "readonly": true - }, - { - "kind": "field", - "name": "noSources", - "readonly": true - }, - { - "kind": "field", - "name": "prevDisabled", - "readonly": true - }, - { - "kind": "field", - "name": "nextDisabled", - "readonly": true - }, - { - "kind": "field", - "name": "info", - "readonly": true - }, - { - "kind": "field", - "name": "pageXofY", - "description": "default x of y text for toolbar", - "return": { - "type": { - "text": "string" - } - }, - "readonly": true - }, - { - "kind": "field", - "name": "navXofY", - "readonly": true - }, - { - "kind": "field", - "name": "defaultToolbars", - "description": "default toolbar config object,\nwhere \"top\" contains config for toolbar above image(s),\nand \"bottom\" contains config for toolbar above image(s)", - "return": { - "type": { - "text": "object" - } - }, - "readonly": true - }, - { - "kind": "method", - "name": "_item", - "parameters": [ - { - "name": "config", - "default": "{}", - "optional": true, - "type": { - "text": "*" - } - }, - { - "name": "top", - "default": "false", - "description": "on top toolbar?", - "optional": true, - "type": { - "text": "boolean" - } - } - ], - "description": "makes a toolbar item from config\n TOOLBAR CONFIG SCHEMA: {\n id : {{itemid / certain ids have default configs and bindings that can be used or overridden}},\n config: {{if item is a button, button config}},\n contents: {{if item is a group, string of text or array of items}},\n }" - }, - { - "kind": "method", - "name": "_group", - "parameters": [ - { - "name": "config", - "default": "{}", - "optional": true, - "type": { - "text": "object" - } - }, - { - "name": "top", - "default": "false", - "description": "on top toolbar?", - "optional": true, - "type": { - "text": "boolean" - } - } - ], - "description": "makes a toolbar group from config\n GROUP CONFIG SCHEMA: {\n id : {{groupid / certain ids have default item groupings that can be used or overridden}},\n type: {{group type to add to classlist}},\n contents: {{sting of text content or array of items in the group}}\n }", - "return": { - "type": { - "text": "" - } - } - }, - { - "kind": "method", - "name": "_button", - "parameters": [ - { - "name": "config", - "default": "{}", - "optional": true, - "type": { - "text": "object" - } - }, - { - "name": "top", - "default": "false", - "description": "on top toolbar?", - "optional": true, - "type": { - "text": "boolean" - } - } - ], - "description": "makes a toolbar button from config\n BUTTON CONFIG SCHEMA: {\n toggleProp : {{optional: if button toggles, property button toggles}},\n enabledProp : {{optional: disable button if prop is false}},\n disabledProp : {{optional: prop to make button disabled}},\n shownProp : {{optional: hide button if prop is false}},\n hiddenProp : {{optional: prop to make button hidden}},\n icon: {{button icon}},\n iconRight: {{show icon to the right of text intead of left}},\n text: {{button text / default tooltip}},\n showText: {{show button text even if button has an icon}},\n tooltip: {{override button text as tooltip}}\n }", - "return": { - "type": { - "text": "" - } - } - }, - { - "kind": "method", - "name": "_buttonDisabled", - "parameters": [ - { - "name": "config" - } - ] - }, - { - "kind": "method", - "name": "_buttonHidden", - "parameters": [ - { - "name": "config" - } - ] - }, - { - "kind": "method", - "name": "_buttonClass", - "parameters": [ - { - "name": "config" - } - ] - }, - { - "kind": "method", - "name": "_buttonInner", - "parameters": [ - { - "name": "config" - } - ] - }, - { - "kind": "method", - "name": "_buttonTooltip", - "parameters": [ - { - "name": "config" - }, - { - "name": "top", - "default": "false" - } - ] - }, - { - "kind": "field", - "name": "src", - "readonly": true - }, - { - "kind": "field", - "name": "loadSrc", - "readonly": true - }, - { - "kind": "field", - "name": "sources", - "readonly": true - }, - { - "kind": "method", - "name": "_setFullscreen", - "parameters": [ - { - "name": "mode" - }, - { - "description": "on or off, default is opposite current state", - "name": "toggle", - "type": { - "text": "boolean" - } - } - ], - "description": "overrides fullscreen API" - }, - { - "kind": "method", - "name": "_toolbarButtonClick", - "parameters": [ - { - "name": "buttonId" - }, - { - "name": "e" - }, - { - "name": "disabled", - "default": "false" - } - ] - }, - { - "kind": "method", - "name": "_xOfYClick", - "parameters": [ - { - "name": "e" - }, - { - "name": "disabled" - } - ] - }, - { - "kind": "method", - "name": "goToPageXofY", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "loadedChangedEvent", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "loadingChangedEvent", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "_addImage" - }, - { - "kind": "method", - "name": "_addTiledImage" - }, - { - "kind": "field", - "name": "minZoomImageRatio", - "type": { - "text": "number" - }, - "default": "1" - }, - { - "kind": "field", - "name": "maxZoomPixelRatio", - "type": { - "text": "number" - }, - "default": "3" - }, - { - "kind": "field", - "name": "__screenfullLoaded", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "__screenfullLoaded" - }, - { - "kind": "field", - "name": "disabled", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "disabled", - "reflects": true - }, - { - "kind": "field", - "name": "figures", - "privacy": "public", - "type": { - "text": "array" - }, - "attribute": "figures" - }, - { - "kind": "field", - "name": "infoToggled", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "whether info mode is toggled", - "attribute": "info-mode", - "reflects": true - }, - { - "kind": "field", - "name": "kbdToggled", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "whether keyboard shortcuts help mode is toggled", - "attribute": "keyboard-help-mode", - "reflects": true - }, - { - "kind": "field", - "name": "toolbars", - "privacy": "public", - "type": { - "text": "object" - }, - "description": "if used with multiple images and paged navigation, index of current item", - "attribute": "toolbars", - "reflects": true - } - ], - "events": [ - { - "name": "toolbar-button-click", - "type": { - "text": "CustomEvent" - }, - "description": "Fires when constructed, so that parent radio group can listen for it." - } - ], - "attributes": [ - { - "name": "disabled", - "type": { - "text": "boolean" - }, - "fieldName": "disabled" - }, - { - "name": "figures", - "type": { - "text": "array" - }, - "fieldName": "figures" - }, - { - "name": "info-mode", - "type": { - "text": "boolean" - }, - "description": "whether info mode is toggled", - "fieldName": "infoToggled" - }, - { - "name": "keyboard-help-mode", - "type": { - "text": "boolean" - }, - "description": "whether keyboard shortcuts help mode is toggled", - "fieldName": "kbdToggled" - }, - { - "name": "toolbars", - "type": { - "text": "object" - }, - "description": "if used with multiple images and paged navigation, index of current item", - "fieldName": "toolbars" - }, - { - "name": "__screenfullLoaded", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "__screenfullLoaded" - } - ], - "mixins": [ - { - "name": "FullscreenBehaviors", - "package": "@haxtheweb/fullscreen-behaviors/fullscreen-behaviors.js" - } - ], - "superclass": { - "name": "ImgPanZoom", - "package": "@haxtheweb/img-pan-zoom/img-pan-zoom.js" - }, - "tagName": "img-view-viewer", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "ImgViewViewer", - "module": "lib/img-view-viewer.js" - } - }, - { - "kind": "js", - "name": "ImgViewViewer", - "declaration": { - "name": "ImgViewViewer", - "module": "lib/img-view-viewer.js" - } - } - ] - } - ] -} diff --git a/elements/img-view-modal/demo/index.html b/elements/img-view-modal/demo/index.html index dfa220d490..f3a252d310 100644 --- a/elements/img-view-modal/demo/index.html +++ b/elements/img-view-modal/demo/index.html @@ -7,7 +7,7 @@ + +

    Basic json-outline-schema demo

    - Toggle debug mode + diff --git a/elements/paper-stepper/index.html b/elements/paper-stepper/index.html index 5610f576fe..09b76fcec8 100755 --- a/elements/paper-stepper/index.html +++ b/elements/paper-stepper/index.html @@ -4,10 +4,10 @@ paper-stepper documentation - - + + - + diff --git a/elements/paper-stepper/package.json b/elements/paper-stepper/package.json old mode 100755 new mode 100644 index dcc678a68c..a02ae8236c --- a/elements/paper-stepper/package.json +++ b/elements/paper-stepper/package.json @@ -43,19 +43,13 @@ "dependencies": { "@haxtheweb/hax-iconset": "^11.0.0", "@haxtheweb/simple-icon": "^11.0.5", - "@polymer/iron-selector": "^3.0.0", - "@polymer/paper-progress": "3.0.1", - "@polymer/paper-styles": "^3.0.0", "@polymer/polymer": "3.5.2" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/parallax-image/.gitignore b/elements/parallax-image/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/parallax-image/.gitignore +++ b/elements/parallax-image/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/parallax-image/custom-elements.json b/elements/parallax-image/custom-elements.json deleted file mode 100755 index 60354aab66..0000000000 --- a/elements/parallax-image/custom-elements.json +++ /dev/null @@ -1,115 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "parallax-image.js", - "declarations": [ - { - "kind": "class", - "description": "`parallax-image`", - "name": "ParallaxImage", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "scrollBy", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "windowControllers", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "imageBg", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Image", - "default": "\"\"", - "attribute": "image-bg", - "reflects": true - }, - { - "kind": "field", - "name": "describedBy", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "Aria-describedby data passed down to appropriate tag", - "attribute": "described-by" - } - ], - "attributes": [ - { - "name": "image-bg", - "type": { - "text": "string" - }, - "description": "Image", - "default": "\"\"", - "fieldName": "imageBg" - }, - { - "name": "described-by", - "type": { - "text": "string" - }, - "description": "Aria-describedby data passed down to appropriate tag", - "fieldName": "describedBy" - } - ], - "mixins": [ - { - "name": "SchemaBehaviors", - "package": "@haxtheweb/schema-behaviors/schema-behaviors.js" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "parallax-image", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "ParallaxImage", - "module": "parallax-image.js" - } - }, - { - "kind": "js", - "name": "ParallaxImage", - "declaration": { - "name": "ParallaxImage", - "module": "parallax-image.js" - } - } - ] - } - ] -} diff --git a/elements/parallax-image/demo/index.html b/elements/parallax-image/demo/index.html index 0742e64cb3..6bf2be723b 100644 --- a/elements/parallax-image/demo/index.html +++ b/elements/parallax-image/demo/index.html @@ -7,7 +7,7 @@ +
    diff --git a/elements/pdf-browser-viewer/index.html b/elements/pdf-browser-viewer/index.html index ff841e8d4d..17265fd559 100755 --- a/elements/pdf-browser-viewer/index.html +++ b/elements/pdf-browser-viewer/index.html @@ -4,10 +4,10 @@ pdf-browser-viewer documentation - - + + - + diff --git a/elements/pdf-browser-viewer/package.json b/elements/pdf-browser-viewer/package.json old mode 100755 new mode 100644 index 396cc93d59..572d3d4606 --- a/elements/pdf-browser-viewer/package.json +++ b/elements/pdf-browser-viewer/package.json @@ -2,7 +2,7 @@ "name": "@haxtheweb/pdf-browser-viewer", "wcfactory": { "className": "PdfBrowserViewer", - "customElementClass": "PolymerElement", + "customElementClass": "LitElement", "elementName": "pdf-browser-viewer", "generator-wcfactory-version": "0.3.2", "useHAX": true, @@ -42,17 +42,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/simple-icon": "^11.0.5", - "@polymer/paper-card": "^3.0.0", - "@polymer/polymer": "3.5.2" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/pdf-browser-viewer/pdf-browser-viewer.js b/elements/pdf-browser-viewer/pdf-browser-viewer.js index 03c41dcc7f..423d6b01af 100644 --- a/elements/pdf-browser-viewer/pdf-browser-viewer.js +++ b/elements/pdf-browser-viewer/pdf-browser-viewer.js @@ -1,5 +1,4 @@ -import { html, PolymerElement } from "@polymer/polymer/polymer-element.js"; -import "@polymer/polymer/lib/elements/dom-if.js"; +import { html, css, LitElement } from "lit"; import "@haxtheweb/simple-icon/simple-icon.js"; import "@haxtheweb/simple-icon/lib/simple-icons.js"; /** @@ -49,55 +48,59 @@ Card example: * @demo demo/index.html */ -class PdfBrowserViewer extends PolymerElement { - static get template() { - return html` - +class PdfBrowserViewer extends LitElement { + static get styles() { + return css` + :host { + display: none; + } + :host([file]) { + display: inherit; + } + div.card { + box-shadow: 0 5px 5px rgba(0, 0, 0, 0.7); + } + `; + } - - - + `} `; } @@ -109,80 +112,68 @@ class PdfBrowserViewer extends PolymerElement { return { /** * The location of the PDF file. - * - * @type String */ file: { type: String, - value: undefined, - reflectToAttribute: true, + reflect: true, }, /** * The message when browser doesn't support pdf object - * - * @type String */ notSupportedMessage: { type: String, - value: - "It appears your Web browser is not configured to display PDF files. No worries, just", }, /** * The PDF link message when browser doesn't support pdf object - * - * @type String */ notSupportedLinkMessage: { type: String, - value: "click here to download the PDF file.", }, /** * The height of the PDF viewer. - * - * @type String */ height: { type: String, - value: "400px", }, /** * The width of the PDF viewer. - * - * @type String */ width: { type: String, - value: "100%", }, /** * PDF viewer as a card with download button. - * - * @type Boolean */ card: { type: Boolean, - value: false, }, /** * Download button label. - * - * @type String */ downloadLabel: { type: String, - value: "Download", }, /** * The z-depth of the card, from 0-5. - * - * @type String */ elevation: { type: String, - value: "1", }, }; } + + constructor() { + super(); + this.file = undefined; + this.notSupportedMessage = + "It appears your Web browser is not configured to display PDF files. No worries, just"; + this.notSupportedLinkMessage = "click here to download the PDF file."; + this.height = "400px"; + this.width = "100%"; + this.card = false; + this.downloadLabel = "Download"; + this.elevation = "1"; + } /** * Clear PDF container */ diff --git a/elements/person-testimonial/.gitignore b/elements/person-testimonial/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/person-testimonial/.gitignore +++ b/elements/person-testimonial/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/person-testimonial/custom-elements.json b/elements/person-testimonial/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/person-testimonial/demo/index.html b/elements/person-testimonial/demo/index.html index 212c7bd824..f122d8d29e 100644 --- a/elements/person-testimonial/demo/index.html +++ b/elements/person-testimonial/demo/index.html @@ -7,10 +7,10 @@ - +
    diff --git a/elements/person-testimonial/index.html b/elements/person-testimonial/index.html index 804c5a90b9..56453e56f8 100755 --- a/elements/person-testimonial/index.html +++ b/elements/person-testimonial/index.html @@ -4,10 +4,10 @@ person-testimonial documentation - - + + - + diff --git a/elements/person-testimonial/package.json b/elements/person-testimonial/package.json old mode 100755 new mode 100644 index b70fcadbf5..4c3b3cd829 --- a/elements/person-testimonial/package.json +++ b/elements/person-testimonial/package.json @@ -38,17 +38,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/simple-colors": "^11.0.5", - "@polymer/iron-image": "^3.0.1", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/place-holder/.gitignore b/elements/place-holder/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/place-holder/.gitignore +++ b/elements/place-holder/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/place-holder/custom-elements.json b/elements/place-holder/custom-elements.json old mode 100755 new mode 100644 index 3b46381220..e87d2d6a86 --- a/elements/place-holder/custom-elements.json +++ b/elements/place-holder/custom-elements.json @@ -43,16 +43,6 @@ ], "description": "Get the calculated text based on text being empty and type being set." }, - { - "kind": "method", - "name": "_getColorFromType", - "parameters": [ - { - "name": "type" - } - ], - "description": "Generate an color based on the media type selected" - }, { "kind": "method", "name": "_getIconFromType", @@ -202,8 +192,8 @@ } ], "superclass": { - "name": "SimpleColors", - "package": "@haxtheweb/simple-colors/simple-colors.js" + "name": "DDD", + "package": "@haxtheweb/d-d-d/d-d-d.js" }, "tagName": "place-holder", "customElement": true diff --git a/elements/place-holder/demo/index.html b/elements/place-holder/demo/index.html index 2c26bef4ac..e771b19f1b 100644 --- a/elements/place-holder/demo/index.html +++ b/elements/place-holder/demo/index.html @@ -7,10 +7,10 @@ - +
    diff --git a/elements/place-holder/index.html b/elements/place-holder/index.html index f4ea2a86af..db67d0ca6d 100755 --- a/elements/place-holder/index.html +++ b/elements/place-holder/index.html @@ -4,10 +4,10 @@ place-holder documentation - - + + - + diff --git a/elements/place-holder/package.json b/elements/place-holder/package.json old mode 100755 new mode 100644 index 81c6eebc20..fc56733911 --- a/elements/place-holder/package.json +++ b/elements/place-holder/package.json @@ -43,16 +43,13 @@ "dependencies": { "@haxtheweb/d-d-d": "^11.0.5", "@haxtheweb/simple-icon": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/place-holder/place-holder.js b/elements/place-holder/place-holder.js index 76ee798c08..8a344661a3 100644 --- a/elements/place-holder/place-holder.js +++ b/elements/place-holder/place-holder.js @@ -26,7 +26,10 @@ class PlaceHolder extends DDD { transition: 0.3s all linear; } :host([drag-over]) { - border: var(--place-holder-drag-over-border, var(--ddd-border-lg) dashed var(--ddd-theme-default-info)); + border: var( + --place-holder-drag-over-border, + var(--ddd-border-lg) dashed var(--ddd-theme-default-info) + ); } .wrapper { text-align: center; @@ -63,9 +66,7 @@ class PlaceHolder extends DDD { render() { return html`
    - +
    ${this.calcText}
    ${this.directions}
    diff --git a/elements/place-holder/test/place-holder.test.js b/elements/place-holder/test/place-holder.test.js index eb82e0e3e6..46846777c9 100644 --- a/elements/place-holder/test/place-holder.test.js +++ b/elements/place-holder/test/place-holder.test.js @@ -5,9 +5,7 @@ import "../place-holder.js"; describe("place-holder test", () => { let element; beforeEach(async () => { - element = await fixture(html` - - `); + element = await fixture(html` `); }); it("passes the a11y audit", async () => { @@ -20,13 +18,13 @@ describe("place-holder test", () => { }); it("should contain wrapper div with proper structure", () => { - const wrapper = element.shadowRoot.querySelector('.wrapper'); + const wrapper = element.shadowRoot.querySelector(".wrapper"); expect(wrapper).to.exist; - - const icon = wrapper.querySelector('simple-icon'); - const textDiv = wrapper.querySelector('.text'); - const directionsDiv = wrapper.querySelector('.directions'); - + + const icon = wrapper.querySelector("simple-icon"); + const textDiv = wrapper.querySelector(".text"); + const directionsDiv = wrapper.querySelector(".directions"); + expect(icon).to.exist; expect(textDiv).to.exist; expect(directionsDiv).to.exist; @@ -34,7 +32,7 @@ describe("place-holder test", () => { it("should extend SimpleColors", () => { expect(element).to.be.instanceOf(PlaceHolder); - expect(element.constructor.name).to.equal('PlaceHolder'); + expect(element.constructor.name).to.equal("PlaceHolder"); }); }); @@ -48,7 +46,9 @@ describe("place-holder test", () => { expect(defaultElement.text).to.equal(""); expect(defaultElement.type).to.equal("text"); expect(defaultElement.dragOver).to.be.false; - expect(defaultElement.directions).to.equal("Drag and drop file to replace"); + expect(defaultElement.directions).to.equal( + "Drag and drop file to replace", + ); expect(defaultElement.iconFromType).to.equal("editor:format-align-left"); }); @@ -65,7 +65,7 @@ describe("place-holder test", () => { it("should update type and recalculate dependent properties", async () => { element.type = "video"; await element.updateComplete; - + expect(element.accentColor).to.equal("red"); expect(element.iconFromType).to.equal("notification:ondemand-video"); expect(element.calcText).to.equal("Placeholder for video"); @@ -74,15 +74,15 @@ describe("place-holder test", () => { it("should update text and reflect in calcText", async () => { element.text = "Custom placeholder text"; await element.updateComplete; - + expect(element.calcText).to.equal("Custom placeholder text"); }); it("should update directions", async () => { element.directions = "Custom directions"; await element.updateComplete; - - const directionsEl = element.shadowRoot.querySelector('.directions'); + + const directionsEl = element.shadowRoot.querySelector(".directions"); expect(directionsEl.textContent).to.equal("Custom directions"); }); }); @@ -95,17 +95,17 @@ describe("place-holder test", () => { { type: "image", color: "orange", icon: "image:crop-original" }, { type: "math", color: "light-blue", icon: "editor:functions" }, { type: "text", color: "indigo", icon: "editor:format-align-left" }, - { type: "unknown", color: "indigo", icon: "editor:format-align-left" } + { type: "unknown", color: "indigo", icon: "editor:format-align-left" }, ]; - typeConfigs.forEach(config => { + typeConfigs.forEach((config) => { it(`should handle type "${config.type}" correctly`, async () => { element.type = config.type; await element.updateComplete; - + expect(element.accentColor).to.equal(config.color); expect(element.iconFromType).to.equal(config.icon); - + if (element.text === "") { expect(element.calcText).to.equal(`Placeholder for ${config.type}`); } @@ -117,8 +117,8 @@ describe("place-holder test", () => { it("should handle dragOver state", async () => { element.dragOver = true; await element.updateComplete; - - expect(element.hasAttribute('drag-over')).to.be.true; + + expect(element.hasAttribute("drag-over")).to.be.true; expect(element.iconFromType).to.equal("icons:file-upload"); expect(element.calcText).to.equal("Drop file to upload"); }); @@ -126,74 +126,74 @@ describe("place-holder test", () => { it("should reset from dragOver state", async () => { element.dragOver = true; await element.updateComplete; - + element.dragOver = false; await element.updateComplete; - - expect(element.hasAttribute('drag-over')).to.be.false; + + expect(element.hasAttribute("drag-over")).to.be.false; expect(element.iconFromType).to.equal("av:music-video"); // audio type from beforeEach expect(element.calcText).to.equal("Placeholder for audio"); }); it("should handle dragover event", () => { - const event = new Event('dragover'); + const event = new Event("dragover"); event.preventDefault = () => {}; event.stopPropagation = () => {}; - + element.dispatchEvent(event); - + expect(element.dragOver).to.be.true; - expect(element.classList.contains('dragover')).to.be.true; + expect(element.classList.contains("dragover")).to.be.true; }); it("should handle dragleave event", () => { element.dragOver = true; - element.classList.add('dragover'); - - const event = new Event('dragleave'); + element.classList.add("dragover"); + + const event = new Event("dragleave"); event.preventDefault = () => {}; event.stopPropagation = () => {}; - + element.dispatchEvent(event); - + expect(element.dragOver).to.be.false; - expect(element.classList.contains('dragover')).to.be.false; + expect(element.classList.contains("dragover")).to.be.false; }); }); describe("Event Handling", () => { it("should fire place-holder-replace event", (done) => { - element.addEventListener('place-holder-replace', (e) => { + element.addEventListener("place-holder-replace", (e) => { expect(e.bubbles).to.be.true; expect(e.cancelable).to.be.true; expect(e.composed).to.be.true; expect(e.detail).to.equal(element.type); done(); }); - + element.fireReplaceEvent(); }); it("should handle drop event with file", (done) => { - const mockFile = new File(['test'], 'test.txt', { type: 'text/plain' }); + const mockFile = new File(["test"], "test.txt", { type: "text/plain" }); const mockDataTransfer = { - items: [{ kind: 'file' }] + items: [{ kind: "file" }], }; - - element.addEventListener('place-holder-file-drop', (e) => { + + element.addEventListener("place-holder-file-drop", (e) => { expect(e.bubbles).to.be.true; expect(e.cancelable).to.be.true; expect(e.composed).to.be.true; expect(e.detail.placeHolderElement).to.equal(element); done(); }); - - const dropEvent = new Event('drop'); + + const dropEvent = new Event("drop"); dropEvent.dataTransfer = mockDataTransfer; dropEvent.preventDefault = () => {}; dropEvent.stopPropagation = () => {}; dropEvent.stopImmediatePropagation = () => {}; - + element.dispatchEvent(dropEvent); }); }); @@ -204,10 +204,10 @@ describe("place-holder test", () => { // Wait for two update cycles to ensure iconFromType is recalculated await element.updateComplete; await element.updateComplete; - - const icon = element.shadowRoot.querySelector('simple-icon'); - expect(icon.getAttribute('icon')).to.equal('notification:ondemand-video'); - expect(icon.getAttribute('accent-color')).to.equal('red'); + + const icon = element.shadowRoot.querySelector("simple-icon"); + expect(icon.getAttribute("icon")).to.equal("notification:ondemand-video"); + expect(icon.getAttribute("accent-color")).to.equal("red"); }); it("should render text content correctly", async () => { @@ -215,17 +215,17 @@ describe("place-holder test", () => { // Need to wait for two update cycles to ensure calcText is updated and rendered await element.updateComplete; await element.updateComplete; - - const textEl = element.shadowRoot.querySelector('.text'); - expect(textEl.textContent).to.equal('Custom text content'); + + const textEl = element.shadowRoot.querySelector(".text"); + expect(textEl.textContent).to.equal("Custom text content"); }); it("should render directions correctly", async () => { element.directions = "Custom directions"; await element.updateComplete; - - const directionsEl = element.shadowRoot.querySelector('.directions'); - expect(directionsEl.textContent).to.equal('Custom directions'); + + const directionsEl = element.shadowRoot.querySelector(".directions"); + expect(directionsEl.textContent).to.equal("Custom directions"); }); }); @@ -233,17 +233,17 @@ describe("place-holder test", () => { it("should apply accent color changes", async () => { element.accentColor = "blue"; await element.updateComplete; - - const icon = element.shadowRoot.querySelector('simple-icon'); - expect(icon.getAttribute('accent-color')).to.equal('blue'); + + const icon = element.shadowRoot.querySelector("simple-icon"); + expect(icon.getAttribute("accent-color")).to.equal("blue"); }); it("should handle dark mode", async () => { element.dark = true; await element.updateComplete; - - const icon = element.shadowRoot.querySelector('simple-icon'); - expect(icon.hasAttribute('dark')).to.be.true; + + const icon = element.shadowRoot.querySelector("simple-icon"); + expect(icon.hasAttribute("dark")).to.be.true; }); }); @@ -256,22 +256,24 @@ describe("place-holder test", () => { // Wait for multiple update cycles to ensure all properties are recalculated await element.updateComplete; await element.updateComplete; - + expect(element.accentColor).to.equal("orange"); expect(element.iconFromType).to.equal("icons:file-upload"); // dragOver overrides type icon expect(element.calcText).to.equal("Drop file to upload"); // dragOver overrides text - - const textEl = element.shadowRoot.querySelector('.text'); - const directionsEl = element.shadowRoot.querySelector('.directions'); - expect(textEl.textContent).to.equal('Drop file to upload'); - expect(directionsEl.textContent).to.equal('JPG, PNG, or GIF formats accepted'); + + const textEl = element.shadowRoot.querySelector(".text"); + const directionsEl = element.shadowRoot.querySelector(".directions"); + expect(textEl.textContent).to.equal("Drop file to upload"); + expect(directionsEl.textContent).to.equal( + "JPG, PNG, or GIF formats accepted", + ); }); }); describe("Accessibility", () => { it("should be accessible with different types", async () => { - const types = ['document', 'audio', 'video', 'image', 'math', 'text']; - + const types = ["document", "audio", "video", "image", "math", "text"]; + for (const type of types) { element.type = type; await element.updateComplete; @@ -294,16 +296,16 @@ describe("place-holder test", () => { describe("Performance and Edge Cases", () => { it("should handle rapid property changes", async () => { - const types = ['audio', 'video', 'image', 'document', 'math', 'text']; - + const types = ["audio", "video", "image", "document", "math", "text"]; + for (const type of types) { element.type = type; } await element.updateComplete; - + // Should end up with the last type - expect(element.type).to.equal('text'); - expect(element.accentColor).to.equal('indigo'); + expect(element.type).to.equal("text"); + expect(element.accentColor).to.equal("indigo"); }); it("should handle empty and null values gracefully", async () => { @@ -311,23 +313,23 @@ describe("place-holder test", () => { element.directions = null; await element.updateComplete; await element.updateComplete; - + // When text is null, the _getCalcText method should treat it as empty and fallback to type - expect(element.calcText).to.be.oneOf([null, 'Placeholder for audio']); - - const directionsEl = element.shadowRoot.querySelector('.directions'); + expect(element.calcText).to.be.oneOf([null, "Placeholder for audio"]); + + const directionsEl = element.shadowRoot.querySelector(".directions"); // When directions is null, it gets converted to empty string in the template - expect(directionsEl.textContent).to.be.oneOf(['null', '']); + expect(directionsEl.textContent).to.be.oneOf(["null", ""]); }); it("should handle undefined values", async () => { element.text = undefined; element.type = undefined; await element.updateComplete; - + // Should fall back to defaults or handle gracefully - expect(element.accentColor).to.equal('indigo'); // default for unknown type - expect(element.iconFromType).to.equal('editor:format-align-left'); + expect(element.accentColor).to.equal("indigo"); // default for unknown type + expect(element.iconFromType).to.equal("editor:format-align-left"); }); it("should handle special characters in text", async () => { @@ -335,8 +337,8 @@ describe("place-holder test", () => { element.text = specialText; await element.updateComplete; await element.updateComplete; - - const textEl = element.shadowRoot.querySelector('.text'); + + const textEl = element.shadowRoot.querySelector(".text"); expect(textEl.textContent).to.equal(specialText); expect(element.calcText).to.equal(specialText); }); @@ -346,84 +348,85 @@ describe("place-holder test", () => { it("should have proper styling structure", () => { const styles = element.constructor.styles; expect(styles).to.exist; - + // Convert styles to string to check for custom properties const styleString = styles.toString(); - expect(styleString).to.include('--place-holder-drag-over-border'); - expect(styleString).to.include('--simple-colors-default-theme-accent-12'); - expect(styleString).to.include('--simple-colors-default-theme-accent-1'); + expect(styleString).to.include("--place-holder-drag-over-border"); + expect(styleString).to.include("--simple-colors-default-theme-accent-12"); + expect(styleString).to.include("--simple-colors-default-theme-accent-1"); }); it("should apply drag-over styling when attribute is present", async () => { element.dragOver = true; await element.updateComplete; - + const computedStyle = getComputedStyle(element); // The drag-over attribute should be present, enabling CSS targeting - expect(element.hasAttribute('drag-over')).to.be.true; + expect(element.hasAttribute("drag-over")).to.be.true; }); }); describe("HAX Integration", () => { it("should have correct HAX properties", () => { const haxProps = PlaceHolder.haxProperties; - + expect(haxProps).to.exist; expect(haxProps.canScale).to.be.false; expect(haxProps.canEditSource).to.be.false; - expect(haxProps.gizmo.title).to.equal('Placeholder'); - expect(haxProps.gizmo.icon).to.equal('hax:placeholder-image'); - expect(haxProps.gizmo.color).to.equal('grey'); + expect(haxProps.gizmo.title).to.equal("Placeholder"); + expect(haxProps.gizmo.icon).to.equal("hax:placeholder-image"); + expect(haxProps.gizmo.color).to.equal("grey"); }); it("should have proper settings configuration", () => { const haxProps = PlaceHolder.haxProperties; const configSettings = haxProps.settings.configure; - + expect(configSettings).to.have.lengthOf(2); - - const typeConfig = configSettings.find(s => s.property === 'type'); - const textConfig = configSettings.find(s => s.property === 'text'); - + + const typeConfig = configSettings.find((s) => s.property === "type"); + const textConfig = configSettings.find((s) => s.property === "text"); + expect(typeConfig).to.exist; - expect(typeConfig.inputMethod).to.equal('select'); - expect(typeConfig.options).to.have.property('image'); - expect(typeConfig.options).to.have.property('video'); - + expect(typeConfig.inputMethod).to.equal("select"); + expect(typeConfig.options).to.have.property("image"); + expect(typeConfig.options).to.have.property("video"); + expect(textConfig).to.exist; - expect(textConfig.inputMethod).to.equal('textfield'); + expect(textConfig.inputMethod).to.equal("textfield"); }); it("should have demo schema", () => { const haxProps = PlaceHolder.haxProperties; const demoSchema = haxProps.demoSchema; - + expect(demoSchema).to.have.lengthOf(1); - expect(demoSchema[0].tag).to.equal('place-holder'); - expect(demoSchema[0].properties.type).to.equal('image'); - expect(demoSchema[0].content).to.equal(''); + expect(demoSchema[0].tag).to.equal("place-holder"); + expect(demoSchema[0].properties.type).to.equal("image"); + expect(demoSchema[0].content).to.equal(""); }); }); describe("Integration Tests", () => { it("should work with direct instantiation", () => { const directElement = new PlaceHolder(); - expect(directElement.tagName.toLowerCase()).to.equal('place-holder'); - expect(directElement.type).to.equal('text'); + expect(directElement.tagName.toLowerCase()).to.equal("place-holder"); + expect(directElement.type).to.equal("text"); }); it("should work when dynamically added to DOM", async () => { - const container = document.createElement('div'); - container.innerHTML = ''; + const container = document.createElement("div"); + container.innerHTML = + ''; document.body.appendChild(container); - - const dynamicElement = container.querySelector('place-holder'); + + const dynamicElement = container.querySelector("place-holder"); await dynamicElement.updateComplete; - - expect(dynamicElement.type).to.equal('video'); - expect(dynamicElement.text).to.equal('Dynamic element'); - expect(dynamicElement.accentColor).to.equal('red'); - + + expect(dynamicElement.type).to.equal("video"); + expect(dynamicElement.text).to.equal("Dynamic element"); + expect(dynamicElement.accentColor).to.equal("red"); + document.body.removeChild(container); }); @@ -431,21 +434,21 @@ describe("place-holder test", () => { const element2 = await fixture(html` `); - + // Modify first element element.type = "document"; element.text = "First element"; await element.updateComplete; - + // Second element should be unaffected - expect(element2.type).to.equal('image'); - expect(element2.text).to.equal('Second element'); - expect(element2.accentColor).to.equal('orange'); - + expect(element2.type).to.equal("image"); + expect(element2.text).to.equal("Second element"); + expect(element2.accentColor).to.equal("orange"); + // First element should have new values - expect(element.type).to.equal('document'); - expect(element.text).to.equal('First element'); - expect(element.accentColor).to.equal('green'); + expect(element.type).to.equal("document"); + expect(element.text).to.equal("First element"); + expect(element.accentColor).to.equal("green"); }); }); }); diff --git a/elements/play-list/.gitignore b/elements/play-list/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/play-list/.gitignore +++ b/elements/play-list/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/play-list/demo/index.html b/elements/play-list/demo/index.html index 0f038f5a4c..856b0a4573 100644 --- a/elements/play-list/demo/index.html +++ b/elements/play-list/demo/index.html @@ -4,15 +4,14 @@ PlayList: play-list Demo - - +
    diff --git a/elements/play-list/index.html b/elements/play-list/index.html index 846278e634..9ef0d7d87b 100644 --- a/elements/play-list/index.html +++ b/elements/play-list/index.html @@ -4,10 +4,10 @@ play-list documentation - - + + - + diff --git a/elements/play-list/package.json b/elements/play-list/package.json index 5362fe064d..c85035d7fb 100644 --- a/elements/play-list/package.json +++ b/elements/play-list/package.json @@ -46,17 +46,14 @@ "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/utils": "^11.0.0", "@shoelace-style/shoelace": "2.8.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@haxtheweb/video-player": "^11.0.5", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "gulp-babel": "8.0.0", "polymer-build": "3.1.4", diff --git a/elements/play-list/test/play-list.test.js b/elements/play-list/test/play-list.test.js index 1a2a476721..528a7683eb 100644 --- a/elements/play-list/test/play-list.test.js +++ b/elements/play-list/test/play-list.test.js @@ -19,12 +19,12 @@ describe("play-list test", () => { it("should extend LitElement", () => { expect(element).to.be.instanceOf(PlayList); - expect(element.constructor.name).to.equal('PlayList'); + expect(element.constructor.name).to.equal("PlayList"); }); it("should have correct tag name", () => { - expect(PlayList.tag).to.equal('play-list'); - expect(element.tagName.toLowerCase()).to.equal('play-list'); + expect(PlayList.tag).to.equal("play-list"); + expect(element.tagName.toLowerCase()).to.equal("play-list"); }); it("should have mutation observer", () => { @@ -40,9 +40,9 @@ describe("play-list test", () => { expect(element.edit).to.be.false; expect(element.navigation).to.be.false; expect(element.pagination).to.be.false; - expect(element.aspectRatio).to.equal('16:9'); + expect(element.aspectRatio).to.equal("16:9"); expect(element.slide).to.equal(0); - expect(element.orientation).to.equal('horizontal'); + expect(element.orientation).to.equal("horizontal"); }); it("should reflect boolean properties to attributes", async () => { @@ -52,91 +52,93 @@ describe("play-list test", () => { element.pagination = true; await element.updateComplete; - expect(element.hasAttribute('loop')).to.be.true; - expect(element.hasAttribute('edit')).to.be.true; - expect(element.hasAttribute('navigation')).to.be.true; - expect(element.hasAttribute('pagination')).to.be.true; + expect(element.hasAttribute("loop")).to.be.true; + expect(element.hasAttribute("edit")).to.be.true; + expect(element.hasAttribute("navigation")).to.be.true; + expect(element.hasAttribute("pagination")).to.be.true; }); it("should reflect string and number properties to attributes", async () => { - element.aspectRatio = '4:3'; - element.orientation = 'vertical'; + element.aspectRatio = "4:3"; + element.orientation = "vertical"; element.slide = 2; await element.updateComplete; - expect(element.getAttribute('aspect-ratio')).to.equal('4:3'); - expect(element.getAttribute('orientation')).to.equal('vertical'); - expect(element.getAttribute('slide')).to.equal('2'); + expect(element.getAttribute("aspect-ratio")).to.equal("4:3"); + expect(element.getAttribute("orientation")).to.equal("vertical"); + expect(element.getAttribute("slide")).to.equal("2"); }); }); describe("Property Updates", () => { it("should update items property", async () => { - const testItems = [{ tag: 'div', properties: {}, content: 'Test content' }]; + const testItems = [ + { tag: "div", properties: {}, content: "Test content" }, + ]; element.items = testItems; await element.updateComplete; - + expect(element.items).to.deep.equal(testItems); }); it("should update loop property", async () => { element.loop = true; await element.updateComplete; - + expect(element.loop).to.be.true; - expect(element.hasAttribute('loop')).to.be.true; + expect(element.hasAttribute("loop")).to.be.true; }); it("should update edit property and change rendering", async () => { element.edit = true; await element.updateComplete; - + expect(element.edit).to.be.true; - const editWrapper = element.shadowRoot.querySelector('.edit-wrapper'); + const editWrapper = element.shadowRoot.querySelector(".edit-wrapper"); expect(editWrapper).to.exist; }); it("should update navigation property", async () => { element.navigation = true; await element.updateComplete; - + expect(element.navigation).to.be.true; }); it("should update pagination property", async () => { element.pagination = true; await element.updateComplete; - + expect(element.pagination).to.be.true; }); it("should update aspectRatio property", async () => { - element.aspectRatio = '21:9'; + element.aspectRatio = "21:9"; await element.updateComplete; - - expect(element.aspectRatio).to.equal('21:9'); + + expect(element.aspectRatio).to.equal("21:9"); }); it("should update orientation property", async () => { - element.orientation = 'vertical'; + element.orientation = "vertical"; await element.updateComplete; - - expect(element.orientation).to.equal('vertical'); - expect(element.hasAttribute('orientation')).to.be.true; + + expect(element.orientation).to.equal("vertical"); + expect(element.hasAttribute("orientation")).to.be.true; }); it("should update slide property and fire event", async () => { let eventFired = false; let eventDetail = null; - - element.addEventListener('slide-changed', (e) => { + + element.addEventListener("slide-changed", (e) => { eventFired = true; eventDetail = e.detail; }); - + element.slide = 3; await element.updateComplete; - + expect(element.slide).to.equal(3); expect(eventFired).to.be.true; expect(eventDetail.value).to.equal(3); @@ -147,29 +149,29 @@ describe("play-list test", () => { it("should render edit mode when edit is true", async () => { element.edit = true; await element.updateComplete; - - const editWrapper = element.shadowRoot.querySelector('.edit-wrapper'); - const carousel = element.shadowRoot.querySelector('sl-carousel'); - + + const editWrapper = element.shadowRoot.querySelector(".edit-wrapper"); + const carousel = element.shadowRoot.querySelector("sl-carousel"); + expect(editWrapper).to.exist; expect(carousel).to.not.exist; - - const slot = editWrapper.querySelector('slot'); + + const slot = editWrapper.querySelector("slot"); expect(slot).to.exist; }); it("should render carousel when items exist and not in edit mode", async () => { const testItems = [ - { tag: 'div', properties: { class: 'item1' }, content: 'Item 1' }, - { tag: 'div', properties: { class: 'item2' }, content: 'Item 2' } + { tag: "div", properties: { class: "item1" }, content: "Item 1" }, + { tag: "div", properties: { class: "item2" }, content: "Item 2" }, ]; element.items = testItems; element.edit = false; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - const editWrapper = element.shadowRoot.querySelector('.edit-wrapper'); - + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + const editWrapper = element.shadowRoot.querySelector(".edit-wrapper"); + expect(carousel).to.exist; expect(editWrapper).to.not.exist; }); @@ -178,10 +180,10 @@ describe("play-list test", () => { element.items = []; element.edit = false; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - const editWrapper = element.shadowRoot.querySelector('.edit-wrapper'); - + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + const editWrapper = element.shadowRoot.querySelector(".edit-wrapper"); + expect(carousel).to.not.exist; expect(editWrapper).to.exist; // Falls back to edit wrapper with empty items }); @@ -190,9 +192,9 @@ describe("play-list test", () => { describe("Carousel Configuration", () => { beforeEach(async () => { const testItems = [ - { tag: 'div', properties: {}, content: 'Item 1' }, - { tag: 'div', properties: {}, content: 'Item 2' }, - { tag: 'div', properties: {}, content: 'Item 3' } + { tag: "div", properties: {}, content: "Item 1" }, + { tag: "div", properties: {}, content: "Item 2" }, + { tag: "div", properties: {}, content: "Item 3" }, ]; element.items = testItems; await element.updateComplete; @@ -200,64 +202,68 @@ describe("play-list test", () => { it("should configure carousel with navigation when enabled", async () => { element.navigation = true; - element.orientation = 'horizontal'; + element.orientation = "horizontal"; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - expect(carousel.hasAttribute('navigation')).to.be.true; + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + expect(carousel.hasAttribute("navigation")).to.be.true; }); it("should not show navigation for vertical orientation", async () => { element.navigation = true; - element.orientation = 'vertical'; + element.orientation = "vertical"; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - expect(carousel.hasAttribute('navigation')).to.be.false; + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + expect(carousel.hasAttribute("navigation")).to.be.false; }); it("should configure carousel with pagination when enabled", async () => { element.pagination = true; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - expect(carousel.hasAttribute('pagination')).to.be.true; + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + expect(carousel.hasAttribute("pagination")).to.be.true; }); it("should configure carousel with loop when enabled", async () => { element.loop = true; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - expect(carousel.hasAttribute('loop')).to.be.true; + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + expect(carousel.hasAttribute("loop")).to.be.true; }); it("should set carousel orientation", async () => { - element.orientation = 'vertical'; + element.orientation = "vertical"; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - expect(carousel.getAttribute('orientation')).to.equal('vertical'); + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + expect(carousel.getAttribute("orientation")).to.equal("vertical"); }); it("should set carousel aspect ratio style", async () => { - element.aspectRatio = '4:3'; + element.aspectRatio = "4:3"; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - expect(carousel.style.getPropertyValue('--aspect-ratio')).to.equal('4:3'); + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + expect(carousel.style.getPropertyValue("--aspect-ratio")).to.equal("4:3"); }); it("should render navigation icons", async () => { await element.updateComplete; - - const prevIcon = element.shadowRoot.querySelector('simple-icon-button-lite[icon="hardware:keyboard-arrow-left"]'); - const nextIcon = element.shadowRoot.querySelector('simple-icon-button-lite[icon="hardware:keyboard-arrow-right"]'); - + + const prevIcon = element.shadowRoot.querySelector( + 'simple-icon-button-lite[icon="hardware:keyboard-arrow-left"]', + ); + const nextIcon = element.shadowRoot.querySelector( + 'simple-icon-button-lite[icon="hardware:keyboard-arrow-right"]', + ); + expect(prevIcon).to.exist; expect(nextIcon).to.exist; - expect(prevIcon.getAttribute('slot')).to.equal('previous-icon'); - expect(nextIcon.getAttribute('slot')).to.equal('next-icon'); + expect(prevIcon.getAttribute("slot")).to.equal("previous-icon"); + expect(nextIcon.getAttribute("slot")).to.equal("next-icon"); }); }); @@ -269,13 +275,13 @@ describe("play-list test", () => {
    Test Item 2
    `); - + // Wait for the mutation observer and mirrorLightDomToItems to process - await new Promise(resolve => setTimeout(resolve, 150)); - + await new Promise((resolve) => setTimeout(resolve, 150)); + expect(testElement.items).to.have.length(2); - expect(testElement.items[0].tag).to.equal('div'); - expect(testElement.items[0].properties.class).to.equal('test-item-1'); + expect(testElement.items[0].tag).to.equal("div"); + expect(testElement.items[0].properties.class).to.equal("test-item-1"); }); it("should handle template wrapper in light DOM", async () => { @@ -287,13 +293,13 @@ describe("play-list test", () => { `); - + // Wait for processing - await new Promise(resolve => setTimeout(resolve, 150)); - + await new Promise((resolve) => setTimeout(resolve, 150)); + // Note: Template elements don't work the same way in tests as in real DOM // The items array should still be processed, but may be empty in test environment - expect(testElement.items).to.be.an('array'); + expect(testElement.items).to.be.an("array"); }); it("should clear items when light DOM is empty", async () => { @@ -302,16 +308,16 @@ describe("play-list test", () => {
    Initial Item
    `); - + // Wait for initial processing - await new Promise(resolve => setTimeout(resolve, 150)); + await new Promise((resolve) => setTimeout(resolve, 150)); expect(testElement.items).to.have.length(1); - + // Clear the content - testElement.innerHTML = ''; - + testElement.innerHTML = ""; + // Wait for mutation observer - await new Promise(resolve => setTimeout(resolve, 150)); + await new Promise((resolve) => setTimeout(resolve, 150)); expect(testElement.items).to.have.length(0); }); @@ -321,18 +327,18 @@ describe("play-list test", () => {
    Initial Item
    `); - + // Wait for initial processing - await new Promise(resolve => setTimeout(resolve, 150)); + await new Promise((resolve) => setTimeout(resolve, 150)); expect(testElement.items).to.have.length(1); - + // Add new item - const newDiv = document.createElement('div'); - newDiv.textContent = 'New Item'; + const newDiv = document.createElement("div"); + newDiv.textContent = "New Item"; testElement.appendChild(newDiv); - + // Wait for mutation observer - await new Promise(resolve => setTimeout(resolve, 150)); + await new Promise((resolve) => setTimeout(resolve, 150)); expect(testElement.items).to.have.length(2); }); }); @@ -340,11 +346,11 @@ describe("play-list test", () => { describe("HAX Item Rendering", () => { it("should render HAX items correctly", () => { const testItem = { - tag: 'div', - properties: { class: 'test-class', id: 'test-id' }, - content: 'Test Content' + tag: "div", + properties: { class: "test-class", id: "test-id" }, + content: "Test Content", }; - + const result = element.renderHAXItem(testItem); expect(result).to.exist; // The result should be a TemplateResult from unsafeHTML @@ -352,14 +358,14 @@ describe("play-list test", () => { it("should remove innerHTML property before rendering", () => { const testItem = { - tag: 'div', - properties: { - class: 'test-class', - innerHTML: 'This should be removed' + tag: "div", + properties: { + class: "test-class", + innerHTML: "This should be removed", }, - content: 'Test Content' + content: "Test Content", }; - + element.renderHAXItem(testItem); expect(testItem.properties.innerHTML).to.be.undefined; }); @@ -368,9 +374,9 @@ describe("play-list test", () => { describe("Slide Management", () => { beforeEach(async () => { const testItems = [ - { tag: 'div', properties: {}, content: 'Item 1' }, - { tag: 'div', properties: {}, content: 'Item 2' }, - { tag: 'div', properties: {}, content: 'Item 3' } + { tag: "div", properties: {}, content: "Item 1" }, + { tag: "div", properties: {}, content: "Item 2" }, + { tag: "div", properties: {}, content: "Item 3" }, ]; element.items = testItems; await element.updateComplete; @@ -378,9 +384,9 @@ describe("play-list test", () => { it("should handle slide index changes from carousel", async () => { const mockEvent = { - detail: { index: 2 } + detail: { index: 2 }, }; - + element.slideIndexChanged(mockEvent); expect(element.slide).to.equal(2); }); @@ -388,15 +394,15 @@ describe("play-list test", () => { it("should fire slide-changed event when slide property changes", async () => { let eventFired = false; let eventDetail = null; - - element.addEventListener('slide-changed', (e) => { + + element.addEventListener("slide-changed", (e) => { eventFired = true; eventDetail = e.detail; }); - + element.slide = 1; await element.updateComplete; - + expect(eventFired).to.be.true; expect(eventDetail.value).to.equal(1); }); @@ -406,34 +412,34 @@ describe("play-list test", () => { it("should have haxProperties defined", () => { const haxProps = PlayList.haxProperties; expect(haxProps).to.exist; - expect(haxProps).to.be.a('string'); - expect(haxProps).to.include('play-list.haxProperties.json'); + expect(haxProps).to.be.a("string"); + expect(haxProps).to.include("play-list.haxProperties.json"); }); it("should have haxHooks method", () => { const hooks = element.haxHooks(); expect(hooks).to.exist; - expect(hooks.inlineContextMenu).to.equal('haxinlineContextMenu'); + expect(hooks.inlineContextMenu).to.equal("haxinlineContextMenu"); }); it("should configure inline context menu", () => { const mockMenu = { ceButtons: [] }; element.haxinlineContextMenu(mockMenu); - + expect(mockMenu.ceButtons).to.have.length(2); - expect(mockMenu.ceButtons[0].icon).to.equal('lrn:edit'); - expect(mockMenu.ceButtons[0].callback).to.equal('haxToggleEdit'); - expect(mockMenu.ceButtons[1].icon).to.equal('hax:anchor'); - expect(mockMenu.ceButtons[1].callback).to.equal('haxClickSlideIndex'); + expect(mockMenu.ceButtons[0].icon).to.equal("lrn:edit"); + expect(mockMenu.ceButtons[0].callback).to.equal("haxToggleEdit"); + expect(mockMenu.ceButtons[1].icon).to.equal("hax:anchor"); + expect(mockMenu.ceButtons[1].callback).to.equal("haxClickSlideIndex"); }); it("should toggle edit mode", async () => { expect(element.edit).to.be.false; - + const result = element.haxToggleEdit(); expect(result).to.be.true; expect(element.edit).to.be.true; - + element.haxToggleEdit(); expect(element.edit).to.be.false; }); @@ -454,24 +460,24 @@ describe("play-list test", () => { called = true; return originalMethod.call(element); }; - + element.firstUpdated(new Map()); expect(called).to.be.true; }); it("should handle disconnectedCallback with existing link elements", () => { // Mock proper link elements that are actually in the head - const link1 = document.createElement('link'); - const link2 = document.createElement('link'); + const link1 = document.createElement("link"); + const link2 = document.createElement("link"); document.head.appendChild(link1); document.head.appendChild(link2); - + element._linkEls = [link1, link2]; - + expect(() => { element.disconnectedCallback(); }).to.not.throw(); - + // Elements should be removed from head expect(document.head.contains(link1)).to.be.false; expect(document.head.contains(link2)).to.be.false; @@ -480,7 +486,7 @@ describe("play-list test", () => { it("should handle disconnectedCallback without link elements", () => { // Ensure _linkEls doesn't exist delete element._linkEls; - + expect(() => { element.disconnectedCallback(); }).to.not.throw(); @@ -490,56 +496,56 @@ describe("play-list test", () => { describe("Accessibility", () => { it("should be accessible with items in carousel mode", async () => { const testItems = [ - { tag: 'div', properties: { role: 'article' }, content: 'Article 1' }, - { tag: 'div', properties: { role: 'article' }, content: 'Article 2' } + { tag: "div", properties: { role: "article" }, content: "Article 1" }, + { tag: "div", properties: { role: "article" }, content: "Article 2" }, ]; element.items = testItems; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); it("should be accessible in edit mode", async () => { element.edit = true; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); it("should be accessible with navigation enabled", async () => { const testItems = [ - { tag: 'div', properties: {}, content: 'Item 1' }, - { tag: 'div', properties: {}, content: 'Item 2' } + { tag: "div", properties: {}, content: "Item 1" }, + { tag: "div", properties: {}, content: "Item 2" }, ]; element.items = testItems; element.navigation = true; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); it("should be accessible with pagination enabled", async () => { const testItems = [ - { tag: 'div', properties: {}, content: 'Item 1' }, - { tag: 'div', properties: {}, content: 'Item 2' }, - { tag: 'div', properties: {}, content: 'Item 3' } + { tag: "div", properties: {}, content: "Item 1" }, + { tag: "div", properties: {}, content: "Item 2" }, + { tag: "div", properties: {}, content: "Item 3" }, ]; element.items = testItems; element.pagination = true; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); it("should be accessible in vertical orientation", async () => { const testItems = [ - { tag: 'div', properties: {}, content: 'Item 1' }, - { tag: 'div', properties: {}, content: 'Item 2' } + { tag: "div", properties: {}, content: "Item 1" }, + { tag: "div", properties: {}, content: "Item 2" }, ]; element.items = testItems; - element.orientation = 'vertical'; + element.orientation = "vertical"; await element.updateComplete; - + await expect(element).shadowDom.to.be.accessible(); }); }); @@ -548,43 +554,43 @@ describe("play-list test", () => { it("should have proper base styling", () => { const styles = element.constructor.styles; expect(styles).to.exist; - + const styleString = styles.toString(); - expect(styleString).to.include(':host'); - expect(styleString).to.include('display: block'); + expect(styleString).to.include(":host"); + expect(styleString).to.include("display: block"); }); it("should have vertical orientation styles", () => { const styles = element.constructor.styles; const styleString = styles.toString(); - + expect(styleString).to.include(':host([orientation="vertical"])'); - expect(styleString).to.include('max-height: 400px'); + expect(styleString).to.include("max-height: 400px"); }); it("should have edit mode styles", () => { const styles = element.constructor.styles; const styleString = styles.toString(); - - expect(styleString).to.include(':host([edit]) .edit-wrapper'); - expect(styleString).to.include('border: 2px dashed #999999'); - expect(styleString).to.include('Play list edit mode'); + + expect(styleString).to.include(":host([edit]) .edit-wrapper"); + expect(styleString).to.include("border: 2px dashed #999999"); + expect(styleString).to.include("Play list edit mode"); }); it("should apply vertical orientation class", async () => { - element.orientation = 'vertical'; + element.orientation = "vertical"; await element.updateComplete; - - expect(element.hasAttribute('orientation')).to.be.true; - expect(element.getAttribute('orientation')).to.equal('vertical'); + + expect(element.hasAttribute("orientation")).to.be.true; + expect(element.getAttribute("orientation")).to.equal("vertical"); }); it("should apply edit mode class", async () => { element.edit = true; await element.updateComplete; - - expect(element.hasAttribute('edit')).to.be.true; - const editWrapper = element.shadowRoot.querySelector('.edit-wrapper'); + + expect(element.hasAttribute("edit")).to.be.true; + const editWrapper = element.shadowRoot.querySelector(".edit-wrapper"); expect(editWrapper).to.exist; }); }); @@ -593,56 +599,56 @@ describe("play-list test", () => { it("should handle empty items array", async () => { element.items = []; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); + + const carousel = element.shadowRoot.querySelector("sl-carousel"); expect(carousel).to.not.exist; }); it("should handle null items", async () => { element.items = null; await element.updateComplete; - + expect(() => element.updateComplete).to.not.throw(); }); it("should handle invalid slide index", async () => { - const testItems = [{ tag: 'div', properties: {}, content: 'Item 1' }]; + const testItems = [{ tag: "div", properties: {}, content: "Item 1" }]; element.items = testItems; element.slide = -1; await element.updateComplete; - + expect(element.slide).to.equal(-1); // Should not crash }); it("should handle items with missing properties", () => { const testItem = { - tag: 'div', - content: 'Test Content' + tag: "div", + content: "Test Content", // Missing properties object }; - + expect(() => { element.renderHAXItem(testItem); }).to.not.throw(); }); it("should handle rapid property changes", async () => { - element.orientation = 'vertical'; + element.orientation = "vertical"; element.loop = true; element.navigation = true; element.pagination = true; element.edit = true; - element.aspectRatio = '1:1'; + element.aspectRatio = "1:1"; element.slide = 5; - + await element.updateComplete; - - expect(element.orientation).to.equal('vertical'); + + expect(element.orientation).to.equal("vertical"); expect(element.loop).to.be.true; expect(element.navigation).to.be.true; expect(element.pagination).to.be.true; expect(element.edit).to.be.true; - expect(element.aspectRatio).to.equal('1:1'); + expect(element.aspectRatio).to.equal("1:1"); expect(element.slide).to.equal(5); }); @@ -654,20 +660,20 @@ describe("play-list test", () => { it("should handle missing shadowRoot in updated", () => { const originalShadowRoot = element.shadowRoot; - Object.defineProperty(element, 'shadowRoot', { + Object.defineProperty(element, "shadowRoot", { get: () => null, - configurable: true + configurable: true, }); - + expect(() => { element.slide = 1; - element.updated(new Map([['slide', 0]])); + element.updated(new Map([["slide", 0]])); }).to.not.throw(); - + // Restore - Object.defineProperty(element, 'shadowRoot', { + Object.defineProperty(element, "shadowRoot", { get: () => originalShadowRoot, - configurable: true + configurable: true, }); }); }); @@ -677,78 +683,78 @@ describe("play-list test", () => { // Start in edit mode element.edit = true; await element.updateComplete; - - let editWrapper = element.shadowRoot.querySelector('.edit-wrapper'); + + let editWrapper = element.shadowRoot.querySelector(".edit-wrapper"); expect(editWrapper).to.exist; - + // Add items and switch to display mode const testItems = [ - { tag: 'div', properties: {}, content: 'Item 1' }, - { tag: 'div', properties: {}, content: 'Item 2' } + { tag: "div", properties: {}, content: "Item 1" }, + { tag: "div", properties: {}, content: "Item 2" }, ]; element.items = testItems; element.edit = false; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); - editWrapper = element.shadowRoot.querySelector('.edit-wrapper'); - + + const carousel = element.shadowRoot.querySelector("sl-carousel"); + editWrapper = element.shadowRoot.querySelector(".edit-wrapper"); + expect(carousel).to.exist; expect(editWrapper).to.not.exist; }); it("should handle multiple slide changes", async () => { const testItems = [ - { tag: 'div', properties: {}, content: 'Item 1' }, - { tag: 'div', properties: {}, content: 'Item 2' }, - { tag: 'div', properties: {}, content: 'Item 3' } + { tag: "div", properties: {}, content: "Item 1" }, + { tag: "div", properties: {}, content: "Item 2" }, + { tag: "div", properties: {}, content: "Item 3" }, ]; element.items = testItems; await element.updateComplete; - + let eventCount = 0; - element.addEventListener('slide-changed', () => { + element.addEventListener("slide-changed", () => { eventCount++; }); - + element.slide = 1; await element.updateComplete; element.slide = 2; await element.updateComplete; element.slide = 0; await element.updateComplete; - + expect(eventCount).to.equal(3); }); it("should work with complex HAX items", async () => { const complexItems = [ { - tag: 'video-player', + tag: "video-player", properties: { - src: 'test.mp4', + src: "test.mp4", controls: true, - width: '100%' + width: "100%", }, - content: '' + content: "", }, { - tag: 'simple-card', + tag: "simple-card", properties: { - title: 'Test Card', - 'accent-color': 'blue' + title: "Test Card", + "accent-color": "blue", }, - content: 'Card content here' - } + content: "Card content here", + }, ]; - + element.items = complexItems; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); + + const carousel = element.shadowRoot.querySelector("sl-carousel"); expect(carousel).to.exist; - - const carouselItems = carousel.querySelectorAll('sl-carousel-item'); + + const carouselItems = carousel.querySelectorAll("sl-carousel-item"); expect(carouselItems).to.have.length(2); }); }); @@ -761,24 +767,24 @@ describe("play-list test", () => { callCount++; return originalMethod.call(element); }; - + // Simulate multiple rapid mutations const testElement = await fixture(html`
    Item 1
    `); - + // Add multiple items rapidly for (let i = 0; i < 5; i++) { - const div = document.createElement('div'); + const div = document.createElement("div"); div.textContent = `Item ${i + 2}`; testElement.appendChild(div); } - + // Wait for debounce timeout - await new Promise(resolve => setTimeout(resolve, 150)); - + await new Promise((resolve) => setTimeout(resolve, 150)); + // Should be called less than the number of mutations due to debouncing expect(callCount).to.be.lessThan(6); }); @@ -787,19 +793,19 @@ describe("play-list test", () => { const manyItems = []; for (let i = 0; i < 100; i++) { manyItems.push({ - tag: 'div', + tag: "div", properties: { class: `item-${i}` }, - content: `Item ${i}` + content: `Item ${i}`, }); } - + element.items = manyItems; await element.updateComplete; - - const carousel = element.shadowRoot.querySelector('sl-carousel'); + + const carousel = element.shadowRoot.querySelector("sl-carousel"); expect(carousel).to.exist; - - const carouselItems = carousel.querySelectorAll('sl-carousel-item'); + + const carouselItems = carousel.querySelectorAll("sl-carousel-item"); expect(carouselItems).to.have.length(100); }); }); diff --git a/elements/polaris-theme/.gitignore b/elements/polaris-theme/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/polaris-theme/.gitignore +++ b/elements/polaris-theme/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/polaris-theme/custom-elements.json b/elements/polaris-theme/custom-elements.json deleted file mode 100644 index 6d5259be1b..0000000000 --- a/elements/polaris-theme/custom-elements.json +++ /dev/null @@ -1,1522 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "polaris-theme.js", - "declarations": [ - { - "kind": "class", - "description": "`polaris-theme`\n`A 2nd polaris theme`", - "name": "PolarisTheme", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true - }, - { - "kind": "method", - "name": "appStoreReady", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "siteModalClick", - "parameters": [ - { - "name": "e" - } - ], - "description": "Delay importing site-search until we click to open it directly" - }, - { - "kind": "field", - "name": "windowControllersLoaded", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "autoScroll", - "type": { - "text": "boolean" - }, - "default": "true" - }, - { - "kind": "field", - "name": "searchTerm", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "searchTerm" - }, - { - "kind": "field", - "name": "imageAlt", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageAlt" - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "image" - }, - { - "kind": "field", - "name": "imageLink", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageLink" - }, - { - "kind": "field", - "name": "__disposer" - }, - { - "kind": "field", - "name": "siteDescription", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "siteDescription" - }, - { - "kind": "field", - "name": "pageTimestamp", - "privacy": "public", - "type": { - "text": "number" - }, - "attribute": "pageTimestamp" - } - ], - "attributes": [ - { - "name": "searchTerm", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "searchTerm" - }, - { - "name": "siteDescription", - "type": { - "text": "string" - }, - "fieldName": "siteDescription" - }, - { - "name": "imageLink", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageLink" - }, - { - "name": "image", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "image" - }, - { - "name": "imageAlt", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageAlt" - }, - { - "name": "pageTimestamp", - "type": { - "text": "number" - }, - "fieldName": "pageTimestamp" - } - ], - "mixins": [ - { - "name": "HAXCMSOperationButtons", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSOperationButtons.js" - }, - { - "name": "PDFPageMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/PDFPageMixin.js" - }, - { - "name": "PrintBranchMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/PrintBranchMixin.js" - }, - { - "name": "QRCodeMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/QRCodeMixin.js" - }, - { - "name": "HAXCMSThemeParts", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSThemeParts.js" - }, - { - "name": "HAXCMSMobileMenuMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSMobileMenu.js" - }, - { - "name": "DDDSuper", - "package": "@haxtheweb/d-d-d/d-d-d.js" - } - ], - "superclass": { - "name": "HAXCMSLitElementTheme", - "package": "@haxtheweb/haxcms-elements/lib/core/HAXCMSLitElementTheme.js" - }, - "tagName": "polaris-theme", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisTheme", - "module": "polaris-theme.js" - } - }, - { - "kind": "js", - "name": "PolarisTheme", - "declaration": { - "name": "PolarisTheme", - "module": "polaris-theme.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/polaris-cta.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "PolarisCta", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "text", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "text" - }, - { - "kind": "field", - "name": "link", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "link" - }, - { - "kind": "field", - "name": "type", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"tinted\"", - "attribute": "type", - "reflects": true - }, - { - "kind": "field", - "name": "outlined", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "outlined", - "reflects": true - }, - { - "kind": "field", - "name": "filled", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "filled", - "reflects": true - } - ], - "attributes": [ - { - "name": "text", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "text" - }, - { - "name": "link", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "link" - }, - { - "name": "type", - "type": { - "text": "string" - }, - "default": "\"tinted\"", - "fieldName": "type" - }, - { - "name": "outlined", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "outlined" - }, - { - "name": "filled", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "filled" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "js", - "name": "PolarisCta", - "declaration": { - "name": "PolarisCta", - "module": "lib/polaris-cta.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisCta", - "module": "lib/polaris-cta.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/polaris-flex-sidebar.js", - "declarations": [ - { - "kind": "class", - "description": "`psu-flex-sidebar`\n`PSU theme based on modern flex design system`", - "name": "PolarisFlexSidebar", - "members": [ - { - "kind": "method", - "name": "renderSideBar", - "description": "Overload methods for customization of slots from the base class", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true, - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "method", - "name": "renderBrandMark", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "method", - "name": "renderHeaderSlot", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "method", - "name": "renderFooterContactInformation", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "method", - "name": "renderFooterSecondarySlot", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "method", - "name": "renderFooterPrimarySlot", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "method", - "name": "appStoreReady", - "parameters": [ - { - "name": "e" - } - ], - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "method", - "name": "siteModalClick", - "parameters": [ - { - "name": "e" - } - ], - "description": "Delay importing site-search until we click to open it directly", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "haxTrayAlignment", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "hax-tray-alignment", - "reflects": true, - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "backgroundColor", - "type": { - "text": "string" - }, - "default": "\"light-dark(var(--ddd-accent-6), var(--ddd-primary-4))\"", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "windowControllersLoaded", - "default": "new AbortController()", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "searchTerm", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "searchTerm", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "imageAlt", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageAlt", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "image", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "imageLink", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageLink", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "__disposer", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "siteDescription", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "siteDescription", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "field", - "name": "pageTimestamp", - "privacy": "public", - "type": { - "text": "number" - }, - "attribute": "pageTimestamp", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - } - ], - "superclass": { - "name": "PolarisFlexTheme", - "module": "/lib/polaris-flex-theme" - }, - "tagName": "psu-flex-base", - "customElement": true, - "attributes": [ - { - "name": "hax-tray-alignment", - "type": { - "text": "string" - }, - "fieldName": "haxTrayAlignment", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "name": "searchTerm", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "searchTerm", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "name": "siteDescription", - "type": { - "text": "string" - }, - "fieldName": "siteDescription", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "name": "imageLink", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageLink", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "name": "image", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "image", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "name": "imageAlt", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageAlt", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "name": "pageTimestamp", - "type": { - "text": "number" - }, - "fieldName": "pageTimestamp", - "inheritedFrom": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - } - ] - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisFlexSidebar", - "module": "lib/polaris-flex-sidebar.js" - } - }, - { - "kind": "js", - "name": "PolarisFlexSidebar", - "declaration": { - "name": "PolarisFlexSidebar", - "module": "lib/polaris-flex-sidebar.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/polaris-flex-theme.js", - "declarations": [ - { - "kind": "class", - "description": "`polaris-flex-theme`\n`Polaris theme based on modern flex design system`", - "name": "PolarisFlexTheme", - "members": [ - { - "kind": "method", - "name": "renderBrandMark" - }, - { - "kind": "method", - "name": "renderHeaderSlot" - }, - { - "kind": "method", - "name": "renderSideBar" - }, - { - "kind": "method", - "name": "renderFooterContactInformation" - }, - { - "kind": "method", - "name": "renderFooterSecondarySlot" - }, - { - "kind": "method", - "name": "renderFooterPrimarySlot" - }, - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true - }, - { - "kind": "method", - "name": "appStoreReady", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "siteModalClick", - "parameters": [ - { - "name": "e" - } - ], - "description": "Delay importing site-search until we click to open it directly" - }, - { - "kind": "field", - "name": "haxTrayAlignment", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "hax-tray-alignment", - "reflects": true - }, - { - "kind": "field", - "name": "backgroundColor", - "type": { - "text": "string" - }, - "default": "\"light-dark(var(--ddd-accent-6), var(--ddd-primary-4))\"" - }, - { - "kind": "field", - "name": "windowControllersLoaded", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "searchTerm", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "searchTerm" - }, - { - "kind": "field", - "name": "imageAlt", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageAlt" - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "image" - }, - { - "kind": "field", - "name": "imageLink", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageLink" - }, - { - "kind": "field", - "name": "__disposer" - }, - { - "kind": "field", - "name": "siteDescription", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "siteDescription" - }, - { - "kind": "field", - "name": "pageTimestamp", - "privacy": "public", - "type": { - "text": "number" - }, - "attribute": "pageTimestamp" - } - ], - "attributes": [ - { - "name": "hax-tray-alignment", - "type": { - "text": "string" - }, - "fieldName": "haxTrayAlignment" - }, - { - "name": "searchTerm", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "searchTerm" - }, - { - "name": "siteDescription", - "type": { - "text": "string" - }, - "fieldName": "siteDescription" - }, - { - "name": "imageLink", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageLink" - }, - { - "name": "image", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "image" - }, - { - "name": "imageAlt", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageAlt" - }, - { - "name": "pageTimestamp", - "type": { - "text": "number" - }, - "fieldName": "pageTimestamp" - } - ], - "mixins": [ - { - "name": "LTIResizingMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/LTIResizingMixin.js" - }, - { - "name": "HAXCMSOperationButtons", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSOperationButtons.js" - }, - { - "name": "HAXCMSRememberRoute", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSRememberRoute.js" - }, - { - "name": "PDFPageMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/PDFPageMixin.js" - }, - { - "name": "PrintBranchMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/PrintBranchMixin.js" - }, - { - "name": "QRCodeMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/QRCodeMixin.js" - }, - { - "name": "HAXCMSThemeParts", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSThemeParts.js" - }, - { - "name": "HAXCMSMobileMenuMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSMobileMenu.js" - }, - { - "name": "DDDSuper", - "package": "@haxtheweb/d-d-d/d-d-d.js" - } - ], - "superclass": { - "name": "HAXCMSLitElementTheme", - "package": "@haxtheweb/haxcms-elements/lib/core/HAXCMSLitElementTheme.js" - }, - "tagName": "polaris-flex-theme", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - }, - { - "kind": "js", - "name": "PolarisFlexTheme", - "declaration": { - "name": "PolarisFlexTheme", - "module": "lib/polaris-flex-theme.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/polaris-invent-theme.js", - "declarations": [ - { - "kind": "class", - "description": "`polaris-invent-theme`\n`A 2nd polaris theme`", - "name": "PolarisInventTheme", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true - }, - { - "kind": "method", - "name": "appStoreReady", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "siteModalClick", - "parameters": [ - { - "name": "e" - } - ], - "description": "Delay importing site-search until we click to open it directly" - }, - { - "kind": "field", - "name": "backgroundColor", - "type": { - "text": "string" - }, - "default": "\"light-dark(var(--ddd-accent-6), var(--ddd-primary-4))\"" - }, - { - "kind": "field", - "name": "windowControllersLoaded", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "searchTerm", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "searchTerm" - }, - { - "kind": "field", - "name": "imageAlt", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageAlt" - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "image" - }, - { - "kind": "field", - "name": "imageLink", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "imageLink" - }, - { - "kind": "field", - "name": "__disposer" - }, - { - "kind": "field", - "name": "siteDescription", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "siteDescription" - }, - { - "kind": "field", - "name": "pageTimestamp", - "privacy": "public", - "type": { - "text": "number" - }, - "attribute": "pageTimestamp" - } - ], - "attributes": [ - { - "name": "searchTerm", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "searchTerm" - }, - { - "name": "siteDescription", - "type": { - "text": "string" - }, - "fieldName": "siteDescription" - }, - { - "name": "imageLink", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageLink" - }, - { - "name": "image", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "image" - }, - { - "name": "imageAlt", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "imageAlt" - }, - { - "name": "pageTimestamp", - "type": { - "text": "number" - }, - "fieldName": "pageTimestamp" - } - ], - "mixins": [ - { - "name": "LTIResizingMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/LTIResizingMixin.js" - }, - { - "name": "HAXCMSOperationButtons", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSOperationButtons.js" - }, - { - "name": "HAXCMSRememberRoute", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSRememberRoute.js" - }, - { - "name": "PDFPageMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/PDFPageMixin.js" - }, - { - "name": "PrintBranchMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/PrintBranchMixin.js" - }, - { - "name": "QRCodeMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/QRCodeMixin.js" - }, - { - "name": "HAXCMSThemeParts", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSThemeParts.js" - }, - { - "name": "HAXCMSMobileMenuMixin", - "package": "@haxtheweb/haxcms-elements/lib/core/utils/HAXCMSMobileMenu.js" - }, - { - "name": "DDDSuper", - "package": "@haxtheweb/d-d-d/d-d-d.js" - } - ], - "superclass": { - "name": "HAXCMSLitElementTheme", - "package": "@haxtheweb/haxcms-elements/lib/core/HAXCMSLitElementTheme.js" - }, - "tagName": "polaris-invent-theme", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisInventTheme", - "module": "lib/polaris-invent-theme.js" - } - }, - { - "kind": "js", - "name": "PolarisInventTheme", - "declaration": { - "name": "PolarisInventTheme", - "module": "lib/polaris-invent-theme.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/polaris-mark.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "PolarisMark", - "members": [ - { - "kind": "method", - "name": "renderSource", - "parameters": [ - { - "name": "type" - } - ] - }, - { - "kind": "method", - "name": "renderDark" - }, - { - "kind": "method", - "name": "renderDefault" - }, - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "type", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"default\"", - "attribute": "type" - }, - { - "kind": "field", - "name": "name", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "name" - }, - { - "kind": "field", - "name": "url", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"https://psu.edu/\"", - "attribute": "url" - } - ], - "attributes": [ - { - "name": "type", - "type": { - "text": "string" - }, - "default": "\"default\"", - "fieldName": "type" - }, - { - "name": "name", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "name" - }, - { - "name": "url", - "type": { - "text": "string" - }, - "default": "\"https://psu.edu/\"", - "fieldName": "url" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisMark", - "module": "lib/polaris-mark.js" - } - }, - { - "kind": "js", - "name": "PolarisMark", - "declaration": { - "name": "PolarisMark", - "module": "lib/polaris-mark.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/polaris-story-card.js", - "declarations": [ - { - "kind": "class", - "description": "`polaris-story-card`\n`A polaris PSU based branding styled theme`", - "name": "PolarisStoryCard", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Convention we use", - "readonly": true - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "image" - }, - { - "kind": "field", - "name": "label", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "label" - }, - { - "kind": "field", - "name": "pillar", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "pillar" - } - ], - "attributes": [ - { - "name": "image", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "image" - }, - { - "name": "label", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "label" - }, - { - "name": "pillar", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "pillar" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "polaris-story-card", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisStoryCard", - "module": "lib/polaris-story-card.js" - } - }, - { - "kind": "js", - "name": "PolarisStoryCard", - "declaration": { - "name": "PolarisStoryCard", - "module": "lib/polaris-story-card.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/polaris-tile.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "PolarisTile", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "type", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "type", - "reflects": true - }, - { - "kind": "field", - "name": "line1", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "line1" - }, - { - "kind": "field", - "name": "line2", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "line2" - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "image" - }, - { - "kind": "field", - "name": "link", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "link" - } - ], - "attributes": [ - { - "name": "type", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "type" - }, - { - "name": "line1", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "line1" - }, - { - "name": "line2", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "line2" - }, - { - "name": "image", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "image" - }, - { - "name": "link", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "link" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "js", - "name": "PolarisTile", - "declaration": { - "name": "PolarisTile", - "module": "lib/polaris-tile.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "PolarisTile", - "module": "lib/polaris-tile.js" - } - } - ] - } - ] -} diff --git a/elements/polaris-theme/demo/index.html b/elements/polaris-theme/demo/index.html index c636d20cbe..aa7440ffc0 100644 --- a/elements/polaris-theme/demo/index.html +++ b/elements/polaris-theme/demo/index.html @@ -4,11 +4,10 @@ PolarisTheme: polaris-theme Demo - diff --git a/elements/polaris-theme/index.html b/elements/polaris-theme/index.html index d66d675fa5..332ff51aa4 100644 --- a/elements/polaris-theme/index.html +++ b/elements/polaris-theme/index.html @@ -4,10 +4,10 @@ polaris-theme documentation - - + + - + diff --git a/elements/polaris-theme/lib/polaris-flex-sidebar.js b/elements/polaris-theme/lib/polaris-flex-sidebar.js index adb2c5ff75..5be6c49ce4 100644 --- a/elements/polaris-theme/lib/polaris-flex-sidebar.js +++ b/elements/polaris-theme/lib/polaris-flex-sidebar.js @@ -14,6 +14,8 @@ import "@haxtheweb/haxcms-elements/lib/ui-components/blocks/site-children-block. * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Website + * @haxcms-theme-internal false * @demo demo/index.html * @element psu-flex-base */ @@ -43,10 +45,22 @@ class PolarisFlexSidebar extends PolarisFlexTheme { --site-children-block-border-bottom: var(--ddd-theme-default-pughBlue) var(--ddd-border-size-xs) solid; --site-children-block-li-padding: var(--ddd-spacing-2) 0; - --site-children-block-link-color: light-dark(var(--ddd-primary-4), var(--ddd-accent-6)); - --site-children-block-link-hover-color: light-dark(var(--ddd-theme-default-link), var(--ddd-theme-default-skyBlue)); - --site-children-block-link-active-bg: light-dark(rgba(0, 30, 68, 0.1), rgba(255, 255, 255, 0.1)); - --site-children-block-active-border-left: light-dark(var(--ddd-theme-default-link), var(--ddd-theme-default-skyBlue)) + --site-children-block-link-color: light-dark( + var(--ddd-primary-4), + var(--ddd-accent-6) + ); + --site-children-block-link-hover-color: light-dark( + var(--ddd-theme-default-link), + var(--ddd-theme-default-skyBlue) + ); + --site-children-block-link-active-bg: light-dark( + rgba(0, 30, 68, 0.1), + rgba(255, 255, 255, 0.1) + ); + --site-children-block-active-border-left: light-dark( + var(--ddd-theme-default-link), + var(--ddd-theme-default-skyBlue) + ) var(--ddd-border-size-md) solid; --site-children-block-link-active-color: light-dark( var(--ddd-theme-default-link), @@ -61,9 +75,18 @@ class PolarisFlexSidebar extends PolarisFlexTheme { } site-breadcrumb { - --site-breadcrumb-color: light-dark(var(--ddd-theme-default-link), var(--ddd-theme-default-skyBlue)); - --site-breadcrumb-color-hover: light-dark(var(--ddd-theme-default-link), var(--ddd-theme-default-skyBlue)); - --site-breadcrumb-decoration-color-hover: light-dark(var(--ddd-theme-default-link), var(--ddd-theme-default-skyBlue)); + --site-breadcrumb-color: light-dark( + var(--ddd-theme-default-link), + var(--ddd-theme-default-skyBlue) + ); + --site-breadcrumb-color-hover: light-dark( + var(--ddd-theme-default-link), + var(--ddd-theme-default-skyBlue) + ); + --site-breadcrumb-decoration-color-hover: light-dark( + var(--ddd-theme-default-link), + var(--ddd-theme-default-skyBlue) + ); } :host([responsive-size="xl"]) { @@ -141,6 +164,19 @@ class PolarisFlexSidebar extends PolarisFlexTheme { `; } + /** + * Handle edit mode changes and force menu to close to prevent clipping + */ + _editModeChanged(newValue, oldValue) { + if (super._editModeChanged) { + super._editModeChanged(newValue, oldValue); + } + // Force close the mobile menu when entering edit mode to prevent clipping + if (newValue === true && this.menuOpen) { + this.__HAXCMSMobileMenuToggle(); + } + } + /** * Store the tag name to make it easier to obtain directly. * @notice function name must be here for tooling to operate correctly diff --git a/elements/polaris-theme/lib/polaris-flex-theme.js b/elements/polaris-theme/lib/polaris-flex-theme.js index 26c8ef3e0f..254f38da13 100644 --- a/elements/polaris-theme/lib/polaris-flex-theme.js +++ b/elements/polaris-theme/lib/polaris-flex-theme.js @@ -34,6 +34,8 @@ import { LTIResizingMixin } from "@haxtheweb/haxcms-elements/lib/core/utils/LTIR * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Website + * @haxcms-theme-internal false * @demo demo/index.html * @element polaris-flex-theme */ @@ -977,6 +979,19 @@ class PolarisFlexTheme extends LTIResizingMixin( }); } + /** + * Handle edit mode changes and force menu to close to prevent clipping + */ + _editModeChanged(newValue, oldValue) { + if (super._editModeChanged) { + super._editModeChanged(newValue, oldValue); + } + // Force close the mobile menu when entering edit mode to prevent clipping + if (newValue === true && this.menuOpen) { + this.__HAXCMSMobileMenuToggle(); + } + } + /** * Delay importing site-search until we click to open it directly */ diff --git a/elements/polaris-theme/lib/polaris-invent-theme.js b/elements/polaris-theme/lib/polaris-invent-theme.js index 521c05d7e7..13fadfecdc 100644 --- a/elements/polaris-theme/lib/polaris-invent-theme.js +++ b/elements/polaris-theme/lib/polaris-invent-theme.js @@ -33,6 +33,8 @@ import { LTIResizingMixin } from "@haxtheweb/haxcms-elements/lib/core/utils/LTIR * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Website + * @haxcms-theme-internal false * @demo demo/index.html * @element polaris-invent-theme */ diff --git a/elements/polaris-theme/package.json b/elements/polaris-theme/package.json index dca6ce5038..f8b8e74185 100644 --- a/elements/polaris-theme/package.json +++ b/elements/polaris-theme/package.json @@ -45,16 +45,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/haxcms-elements": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "gulp-babel": "8.0.0", "polymer-build": "3.1.4", diff --git a/elements/polaris-theme/polaris-theme.js b/elements/polaris-theme/polaris-theme.js index a191119eb1..85df055ed9 100644 --- a/elements/polaris-theme/polaris-theme.js +++ b/elements/polaris-theme/polaris-theme.js @@ -30,6 +30,8 @@ import { DDDSuper } from "@haxtheweb/d-d-d/d-d-d.js"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Website + * @haxcms-theme-internal false * @demo demo/index.html * @element polaris-theme */ diff --git a/elements/polaris-theme/test/polaris-theme.test.js b/elements/polaris-theme/test/polaris-theme.test.js index b433624aaa..ac2415a091 100644 --- a/elements/polaris-theme/test/polaris-theme.test.js +++ b/elements/polaris-theme/test/polaris-theme.test.js @@ -1,82 +1,84 @@ -import { html } from 'lit'; -import { fixture, expect } from '@open-wc/testing'; -import '../polaris-theme.js'; +import { html } from "lit"; +import { fixture, expect } from "@open-wc/testing"; +import "../polaris-theme.js"; -describe('polaris-theme test', () => { +describe("polaris-theme test", () => { let element; beforeEach(async () => { element = await fixture(html``); }); - it('should exist as a custom element', () => { - expect(customElements.get('polaris-theme')).to.exist; + it("should exist as a custom element", () => { + expect(customElements.get("polaris-theme")).to.exist; }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; - expect(element.tagName.toLowerCase()).to.equal('polaris-theme'); + expect(element.tagName.toLowerCase()).to.equal("polaris-theme"); }); - it('should have shadow DOM', () => { + it("should have shadow DOM", () => { expect(element.shadowRoot).to.exist; }); - it('should have default properties', () => { - expect(element.searchTerm).to.equal(''); - expect(element.imageAlt).to.equal(''); - expect(element.image).to.equal(''); - expect(element.imageLink).to.equal(''); + it("should have default properties", () => { + expect(element.searchTerm).to.equal(""); + expect(element.imageAlt).to.equal(""); + expect(element.image).to.equal(""); + expect(element.imageLink).to.equal(""); expect(element.editMode).to.be.false; }); - it('should update properties', async () => { - element.searchTerm = 'test search'; - element.imageAlt = 'Test image'; - element.image = '/test.jpg'; - element.imageLink = '/test-link'; + it("should update properties", async () => { + element.searchTerm = "test search"; + element.imageAlt = "Test image"; + element.image = "/test.jpg"; + element.imageLink = "/test-link"; await element.updateComplete; - - expect(element.searchTerm).to.equal('test search'); - expect(element.imageAlt).to.equal('Test image'); - expect(element.image).to.equal('/test.jpg'); - expect(element.imageLink).to.equal('/test-link'); + + expect(element.searchTerm).to.equal("test search"); + expect(element.imageAlt).to.equal("Test image"); + expect(element.image).to.equal("/test.jpg"); + expect(element.imageLink).to.equal("/test-link"); }); - it('should render basic structure', () => { + it("should render basic structure", () => { const shadowRoot = element.shadowRoot; - expect(shadowRoot.querySelector('header')).to.exist; - expect(shadowRoot.querySelector('nav')).to.exist; - expect(shadowRoot.querySelector('main')).to.exist; - expect(shadowRoot.querySelector('aside')).to.exist; - expect(shadowRoot.querySelector('footer')).to.exist; + expect(shadowRoot.querySelector("header")).to.exist; + expect(shadowRoot.querySelector("nav")).to.exist; + expect(shadowRoot.querySelector("main")).to.exist; + expect(shadowRoot.querySelector("aside")).to.exist; + expect(shadowRoot.querySelector("footer")).to.exist; }); - it('should handle edit mode toggle', async () => { + it("should handle edit mode toggle", async () => { element.editMode = true; await element.updateComplete; expect(element.editMode).to.be.true; - + element.editMode = false; await element.updateComplete; expect(element.editMode).to.be.false; }); - it('should handle null property values gracefully', async () => { + it("should handle null property values gracefully", async () => { element.image = null; await element.updateComplete; - + // Should handle null gracefully expect(element.image).to.be.null; }); - it('should support multiple instances', async () => { - const element2 = await fixture(html``); - - expect(element.image).to.equal(''); - expect(element2.image).to.equal('/test.jpg'); + it("should support multiple instances", async () => { + const element2 = await fixture( + html``, + ); + + expect(element.image).to.equal(""); + expect(element2.image).to.equal("/test.jpg"); }); - it('should cleanup on disconnection', () => { + it("should cleanup on disconnection", () => { expect(() => element.disconnectedCallback()).to.not.throw; }); }); diff --git a/elements/portal-launcher/.gitignore b/elements/portal-launcher/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/portal-launcher/.gitignore +++ b/elements/portal-launcher/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/portal-launcher/custom-elements.json b/elements/portal-launcher/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/portal-launcher/demo/index.html b/elements/portal-launcher/demo/index.html index ebfba2345d..a9fee54f1c 100644 --- a/elements/portal-launcher/demo/index.html +++ b/elements/portal-launcher/demo/index.html @@ -9,10 +9,10 @@ - +
    diff --git a/elements/portal-launcher/index.html b/elements/portal-launcher/index.html index f804786c90..0e454c0943 100755 --- a/elements/portal-launcher/index.html +++ b/elements/portal-launcher/index.html @@ -4,10 +4,10 @@ portal-launcher documentation - - + + - + diff --git a/elements/portal-launcher/package.json b/elements/portal-launcher/package.json old mode 100755 new mode 100644 index 81f7993007..555b6dbfb5 --- a/elements/portal-launcher/package.json +++ b/elements/portal-launcher/package.json @@ -44,10 +44,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/portal-launcher/test/portal-launcher.test.js b/elements/portal-launcher/test/portal-launcher.test.js index 1b181c2511..8ab8c79f0a 100644 --- a/elements/portal-launcher/test/portal-launcher.test.js +++ b/elements/portal-launcher/test/portal-launcher.test.js @@ -1,11 +1,11 @@ import { fixture, expect, html } from "@open-wc/testing"; -import { sendKeys } from '@web/test-runner-commands'; +import { sendKeys } from "@web/test-runner-commands"; import "../portal-launcher.js"; // Simple spy utility function createSpy() { let calls = []; - let fn = function(...args) { + let fn = function (...args) { calls.push(args); return fn.returnValue; }; @@ -18,7 +18,7 @@ function createSpy() { target.called = true; target.callCount++; return target(...args); - } + }, }); } @@ -32,39 +32,39 @@ describe("portal-launcher test", () => { `); }); - describe('Component Structure', () => { - it('should be defined as a custom element', () => { - expect(customElements.get('portal-launcher')).to.exist; - expect(element.tagName.toLowerCase()).to.equal('portal-launcher'); + describe("Component Structure", () => { + it("should be defined as a custom element", () => { + expect(customElements.get("portal-launcher")).to.exist; + expect(element.tagName.toLowerCase()).to.equal("portal-launcher"); }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; - expect(element.constructor.name).to.equal('PortalLauncher'); + expect(element.constructor.name).to.equal("PortalLauncher"); }); - it('should have correct tag property', () => { - expect(element.tag).to.equal('portal-launcher'); - expect(element.constructor.tag).to.equal('portal-launcher'); + it("should have correct tag property", () => { + expect(element.tag).to.equal("portal-launcher"); + expect(element.constructor.tag).to.equal("portal-launcher"); }); - it('should extend HTMLElement', () => { + it("should extend HTMLElement", () => { expect(element instanceof HTMLElement).to.be.true; }); - it('should not have shadow DOM', () => { + it("should not have shadow DOM", () => { expect(element.shadowRoot).to.be.null; }); }); - describe('Link Detection and Event Binding', () => { - it('should find and bind click events to anchor tags', () => { - const links = element.querySelectorAll('a'); + describe("Link Detection and Event Binding", () => { + it("should find and bind click events to anchor tags", () => { + const links = element.querySelectorAll("a"); expect(links.length).to.equal(1); - expect(links[0].href).to.equal('https://example.com/'); + expect(links[0].href).to.equal("https://example.com/"); }); - it('should handle elements with no links gracefully', async () => { + it("should handle elements with no links gracefully", async () => { const elementWithoutLinks = await fixture(html`
    No links here
    @@ -73,7 +73,7 @@ describe("portal-launcher test", () => { expect(() => elementWithoutLinks).to.not.throw; }); - it('should bind to multiple links', async () => { + it("should bind to multiple links", async () => { const elementWithMultipleLinks = await fixture(html` Link 1 @@ -81,127 +81,127 @@ describe("portal-launcher test", () => { Link 3 `); - const links = elementWithMultipleLinks.querySelectorAll('a'); + const links = elementWithMultipleLinks.querySelectorAll("a"); expect(links.length).to.equal(3); }); }); - describe('Event Path Normalization', () => { - it('should normalize composed event paths', () => { + describe("Event Path Normalization", () => { + it("should normalize composed event paths", () => { const mockEvent = { composed: true, - composedPath: () => ['path1', 'path2'] + composedPath: () => ["path1", "path2"], }; const path = element.normalizeEventPath(mockEvent); - expect(path).to.deep.equal(['path1', 'path2']); + expect(path).to.deep.equal(["path1", "path2"]); }); - it('should handle legacy path property', () => { + it("should handle legacy path property", () => { const mockEvent = { - path: ['legacy1', 'legacy2'] + path: ["legacy1", "legacy2"], }; const path = element.normalizeEventPath(mockEvent); - expect(path).to.deep.equal(['legacy1', 'legacy2']); + expect(path).to.deep.equal(["legacy1", "legacy2"]); }); - it('should fallback to originalTarget', () => { + it("should fallback to originalTarget", () => { const mockEvent = { - originalTarget: 'original' + originalTarget: "original", }; const path = element.normalizeEventPath(mockEvent); - expect(path).to.deep.equal(['original']); + expect(path).to.deep.equal(["original"]); }); - it('should fallback to target', () => { + it("should fallback to target", () => { const mockEvent = { - target: 'fallback' + target: "fallback", }; const path = element.normalizeEventPath(mockEvent); - expect(path).to.deep.equal(['fallback']); + expect(path).to.deep.equal(["fallback"]); }); }); - describe('Click Event Handling', () => { + describe("Click Event Handling", () => { let link; beforeEach(() => { - link = element.querySelector('a'); + link = element.querySelector("a"); }); - it('should handle click events on anchor tags', () => { + it("should handle click events on anchor tags", () => { // The click event handler is bound during construction // We can verify the link exists and has the event listener bound expect(link).to.exist; - expect(link.href).to.equal('https://example.com/'); - + expect(link.href).to.equal("https://example.com/"); + // Test that click doesn't throw an error when called expect(() => link.click()).to.not.throw; }); - it('should find correct target from event path', () => { - const mockA = { tagName: 'A', getAttribute: () => 'https://test.com' }; - const mockDiv = { tagName: 'DIV' }; + it("should find correct target from event path", () => { + const mockA = { tagName: "A", getAttribute: () => "https://test.com" }; + const mockDiv = { tagName: "DIV" }; const mockEvent = { target: mockDiv, preventDefault: () => {}, stopPropagation: () => {}, - stopImmediatePropagation: () => {} + stopImmediatePropagation: () => {}, }; - + // Mock the normalizeEventPath to return our test path const originalNormalize = element.normalizeEventPath; element.normalizeEventPath = () => [mockDiv, mockA]; - + element.click(mockEvent); - + // Restore original method element.normalizeEventPath = originalNormalize; }); }); - describe('Portal Enhancement', () => { - it('should handle links without href gracefully', () => { + describe("Portal Enhancement", () => { + it("should handle links without href gracefully", () => { const mockEvent = { - target: { tagName: 'A', getAttribute: () => null }, - preventDefault: createSpy() + target: { tagName: "A", getAttribute: () => null }, + preventDefault: createSpy(), }; - + expect(() => element.click(mockEvent)).to.not.throw; expect(mockEvent.preventDefault.called).to.be.false; }); - it('should check for portal support', () => { + it("should check for portal support", () => { // Test that portal feature detection doesn't throw - const hasPortalSupport = 'HTMLPortalElement' in window; - expect(typeof hasPortalSupport).to.equal('boolean'); + const hasPortalSupport = "HTMLPortalElement" in window; + expect(typeof hasPortalSupport).to.equal("boolean"); }); }); - describe('Fallback Behavior', () => { - it('should handle normal navigation gracefully', () => { - const link = element.querySelector('a'); + describe("Fallback Behavior", () => { + it("should handle normal navigation gracefully", () => { + const link = element.querySelector("a"); const mockEvent = { target: link, preventDefault: createSpy(), - stopPropagation: createSpy() + stopPropagation: createSpy(), }; - + // Test that click handler doesn't throw expect(() => element.click(mockEvent)).to.not.throw; }); }); - describe('Edge Cases and Error Handling', () => { - it('should handle null event gracefully', () => { + describe("Edge Cases and Error Handling", () => { + it("should handle null event gracefully", () => { expect(() => element.normalizeEventPath(null)).to.throw; }); - it('should handle event without target', () => { + it("should handle event without target", () => { const mockEvent = {}; const path = element.normalizeEventPath(mockEvent); expect(path).to.deep.equal([undefined]); }); - it('should handle nested elements correctly', async () => { + it("should handle nested elements correctly", async () => { const nestedElement = await fixture(html`
    @@ -211,36 +211,36 @@ describe("portal-launcher test", () => {
    `); - - const link = nestedElement.querySelector('a'); + + const link = nestedElement.querySelector("a"); expect(link).to.exist; - expect(link.href).to.equal('https://nested.com/'); + expect(link.href).to.equal("https://nested.com/"); }); }); - describe('Multiple Instances', () => { - it('should support multiple portal-launcher instances', async () => { + describe("Multiple Instances", () => { + it("should support multiple portal-launcher instances", async () => { const element1 = await fixture(html` Site 1 `); - + const element2 = await fixture(html` Site 2 `); - - expect(element1.querySelector('a').href).to.equal('https://site1.com/'); - expect(element2.querySelector('a').href).to.equal('https://site2.com/'); + + expect(element1.querySelector("a").href).to.equal("https://site1.com/"); + expect(element2.querySelector("a").href).to.equal("https://site2.com/"); }); }); - describe('Performance', () => { - it('should handle rapid click events efficiently', () => { - const link = element.querySelector('a'); - + describe("Performance", () => { + it("should handle rapid click events efficiently", () => { + const link = element.querySelector("a"); + // Test that rapid clicks don't throw errors expect(() => { for (let i = 0; i < 10; i++) { diff --git a/elements/post-card/.gitignore b/elements/post-card/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/post-card/.gitignore +++ b/elements/post-card/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/post-card/custom-elements.json b/elements/post-card/custom-elements.json deleted file mode 100644 index 9d54a1bbd9..0000000000 --- a/elements/post-card/custom-elements.json +++ /dev/null @@ -1,418 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "post-card.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "PostCard", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "description": "haxProperties integration via file reference", - "readonly": true - }, - { - "kind": "field", - "name": "t", - "privacy": "public", - "type": { - "text": "object" - }, - "default": "{ label: \"Post Card\", send: \"To\", receive: \"From\", }", - "attribute": "t" - }, - { - "kind": "field", - "name": "photoSrc", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "photo-src", - "reflects": true - }, - { - "kind": "field", - "name": "stampSrc", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "stamp-src", - "reflects": true - }, - { - "kind": "field", - "name": "postMarkLocations", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"insert - locations - here\"", - "attribute": "post-mark-locations", - "reflects": true - }, - { - "kind": "field", - "name": "to", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "to", - "reflects": true - }, - { - "kind": "field", - "name": "from", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "from", - "reflects": true - }, - { - "kind": "field", - "name": "message", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "message", - "reflects": true - } - ], - "attributes": [ - { - "name": "t", - "type": { - "text": "object" - }, - "default": "{ label: \"Post Card\", send: \"To\", receive: \"From\", }", - "fieldName": "t" - }, - { - "name": "to", - "type": { - "text": "string" - }, - "fieldName": "to" - }, - { - "name": "from", - "type": { - "text": "string" - }, - "fieldName": "from" - }, - { - "name": "message", - "type": { - "text": "string" - }, - "fieldName": "message" - }, - { - "name": "photo-src", - "type": { - "text": "string" - }, - "fieldName": "photoSrc" - }, - { - "name": "stamp-src", - "type": { - "text": "string" - }, - "fieldName": "stampSrc" - }, - { - "name": "post-mark-locations", - "type": { - "text": "string" - }, - "default": "\"insert - locations - here\"", - "fieldName": "postMarkLocations" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "js", - "name": "PostCard", - "declaration": { - "name": "PostCard", - "module": "post-card.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "PostCard", - "module": "post-card.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/PostCardPhoto.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "PostCardPhoto", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "accentColor", - "type": { - "text": "string" - }, - "default": "\"grey\"" - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "image" - }, - { - "kind": "field", - "name": "alt", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "alt", - "reflects": true - } - ], - "attributes": [ - { - "name": "image", - "type": { - "text": "string" - }, - "fieldName": "image" - }, - { - "name": "alt", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "alt" - } - ], - "superclass": { - "name": "SimpleColors", - "package": "@haxtheweb/simple-colors/simple-colors" - } - } - ], - "exports": [ - { - "kind": "js", - "name": "PostCardPhoto", - "declaration": { - "name": "PostCardPhoto", - "module": "lib/PostCardPhoto.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "PostCardPhoto", - "module": "lib/PostCardPhoto.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/PostCardPostmark.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "PostCardPostmark", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "image" - }, - { - "kind": "field", - "name": "alt", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "alt" - }, - { - "kind": "field", - "name": "locations", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "locations" - } - ], - "attributes": [ - { - "name": "image", - "type": { - "text": "string" - }, - "fieldName": "image" - }, - { - "name": "alt", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "alt" - }, - { - "name": "locations", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "locations" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "js", - "name": "PostCardPostmark", - "declaration": { - "name": "PostCardPostmark", - "module": "lib/PostCardPostmark.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "PostCardPostmark", - "module": "lib/PostCardPostmark.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/PostCardStamp.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "PostCardStamp", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "image", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "image", - "reflects": true - } - ], - "attributes": [ - { - "name": "image", - "type": { - "text": "string" - }, - "fieldName": "image" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "js", - "name": "PostCardStamp", - "declaration": { - "name": "PostCardStamp", - "module": "lib/PostCardStamp.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "PostCardStamp", - "module": "lib/PostCardStamp.js" - } - } - ] - } - ] -} diff --git a/elements/post-card/demo/index.html b/elements/post-card/demo/index.html index d0299da365..1f69c8359c 100644 --- a/elements/post-card/demo/index.html +++ b/elements/post-card/demo/index.html @@ -4,19 +4,18 @@ PostCard: post-card Demo - - +
    diff --git a/elements/post-card/index.html b/elements/post-card/index.html index 92e7a2bfed..c959778de5 100644 --- a/elements/post-card/index.html +++ b/elements/post-card/index.html @@ -4,10 +4,10 @@ post-card documentation - - + + - + diff --git a/elements/post-card/package.json b/elements/post-card/package.json index aa91b11f49..bdbc69cbc2 100644 --- a/elements/post-card/package.json +++ b/elements/post-card/package.json @@ -45,16 +45,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/simple-colors": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/post-card/test/post-card.test.js b/elements/post-card/test/post-card.test.js index a42601c4ad..8026d223ae 100644 --- a/elements/post-card/test/post-card.test.js +++ b/elements/post-card/test/post-card.test.js @@ -18,130 +18,132 @@ describe("PostCard", () => { ); }); - describe('Component Structure', () => { - it('should be defined as a custom element', () => { - expect(customElements.get('post-card')).to.exist; - expect(element.tagName.toLowerCase()).to.equal('post-card'); + describe("Component Structure", () => { + it("should be defined as a custom element", () => { + expect(customElements.get("post-card")).to.exist; + expect(element.tagName.toLowerCase()).to.equal("post-card"); }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; - expect(element.constructor.name).to.equal('PostCard'); + expect(element.constructor.name).to.equal("PostCard"); }); - it('should have correct tag property', () => { - expect(element.constructor.tag).to.equal('post-card'); + it("should have correct tag property", () => { + expect(element.constructor.tag).to.equal("post-card"); }); - it('should extend LitElement', () => { - expect(element.constructor.name).to.equal('PostCard'); + it("should extend LitElement", () => { + expect(element.constructor.name).to.equal("PostCard"); }); - it('should have shadow DOM', () => { + it("should have shadow DOM", () => { expect(element.shadowRoot).to.exist; }); }); - describe('Default Properties', () => { - it('should have default translation object', () => { - expect(element.t).to.be.an('object'); - expect(element.t.label).to.equal('Post Card'); - expect(element.t.send).to.equal('To'); - expect(element.t.receive).to.equal('From'); + describe("Default Properties", () => { + it("should have default translation object", () => { + expect(element.t).to.be.an("object"); + expect(element.t.label).to.equal("Post Card"); + expect(element.t.send).to.equal("To"); + expect(element.t.receive).to.equal("From"); }); - it('should have default image sources', async () => { + it("should have default image sources", async () => { // Create a fresh element without custom photo-src to test defaults const defaultElement = await fixture(html``); - expect(defaultElement.photoSrc).to.include('postcard-photo-stock.jpg'); - expect(defaultElement.stampSrc).to.include('postcard-stamp-stock.jpg'); + expect(defaultElement.photoSrc).to.include("postcard-photo-stock.jpg"); + expect(defaultElement.stampSrc).to.include("postcard-stamp-stock.jpg"); }); - it('should have default post mark locations', async () => { + it("should have default post mark locations", async () => { // Create a fresh element without post-mark-locations to test defaults const defaultElement = await fixture(html``); - expect(defaultElement.postMarkLocations).to.equal('insert - locations - here'); + expect(defaultElement.postMarkLocations).to.equal( + "insert - locations - here", + ); }); }); - describe('Property Updates', () => { - it('should update to property', async () => { - element.to = 'New Destination'; + describe("Property Updates", () => { + it("should update to property", async () => { + element.to = "New Destination"; await element.updateComplete; - expect(element.to).to.equal('New Destination'); + expect(element.to).to.equal("New Destination"); }); - it('should update from property', async () => { - element.from = 'New Origin'; + it("should update from property", async () => { + element.from = "New Origin"; await element.updateComplete; - expect(element.from).to.equal('New Origin'); + expect(element.from).to.equal("New Origin"); }); - it('should update message property', async () => { - element.message = 'New message content'; + it("should update message property", async () => { + element.message = "New message content"; await element.updateComplete; - expect(element.message).to.equal('New message content'); + expect(element.message).to.equal("New message content"); }); - it('should update photo source', async () => { - element.photoSrc = 'http://example.com/new-photo.jpg'; + it("should update photo source", async () => { + element.photoSrc = "http://example.com/new-photo.jpg"; await element.updateComplete; - expect(element.photoSrc).to.equal('http://example.com/new-photo.jpg'); + expect(element.photoSrc).to.equal("http://example.com/new-photo.jpg"); }); - it('should update stamp source', async () => { - element.stampSrc = 'http://example.com/new-stamp.jpg'; + it("should update stamp source", async () => { + element.stampSrc = "http://example.com/new-stamp.jpg"; await element.updateComplete; - expect(element.stampSrc).to.equal('http://example.com/new-stamp.jpg'); + expect(element.stampSrc).to.equal("http://example.com/new-stamp.jpg"); }); - it('should update post mark locations', async () => { - element.postMarkLocations = 'France, Italy'; + it("should update post mark locations", async () => { + element.postMarkLocations = "France, Italy"; await element.updateComplete; - expect(element.postMarkLocations).to.equal('France, Italy'); + expect(element.postMarkLocations).to.equal("France, Italy"); }); }); - describe('Template Rendering', () => { - it('should render main card structure', () => { - const card = element.shadowRoot.querySelector('.entireCard'); + describe("Template Rendering", () => { + it("should render main card structure", () => { + const card = element.shadowRoot.querySelector(".entireCard"); expect(card).to.exist; }); - it('should render background lines', () => { - const bgLines = element.shadowRoot.querySelector('.backgroundLines'); + it("should render background lines", () => { + const bgLines = element.shadowRoot.querySelector(".backgroundLines"); expect(bgLines).to.exist; - const img = bgLines.querySelector('img'); + const img = bgLines.querySelector("img"); expect(img).to.exist; }); - it('should render foreground elements', () => { - const fg = element.shadowRoot.querySelector('.foregroundElements'); + it("should render foreground elements", () => { + const fg = element.shadowRoot.querySelector(".foregroundElements"); expect(fg).to.exist; }); - it('should render postage area', () => { - const postage = element.shadowRoot.querySelector('.postage'); + it("should render postage area", () => { + const postage = element.shadowRoot.querySelector(".postage"); expect(postage).to.exist; }); - it('should render image area', () => { - const image = element.shadowRoot.querySelector('.image'); + it("should render image area", () => { + const image = element.shadowRoot.querySelector(".image"); expect(image).to.exist; }); - it('should render to/from area', () => { - const tofrom = element.shadowRoot.querySelector('.tofrom'); + it("should render to/from area", () => { + const tofrom = element.shadowRoot.querySelector(".tofrom"); expect(tofrom).to.exist; }); - it('should render message area', () => { - const message = element.shadowRoot.querySelector('.message'); + it("should render message area", () => { + const message = element.shadowRoot.querySelector(".message"); expect(message).to.exist; }); }); - describe('Sub-components', () => { + describe("Sub-components", () => { it("renders a post-card-photo", () => { const pcp = element.shadowRoot.querySelector("post-card-photo"); expect(pcp).to.exist; @@ -157,18 +159,18 @@ describe("PostCard", () => { expect(pcpm).to.exist; }); - it('should pass correct attributes to sub-components', () => { - const photo = element.shadowRoot.querySelector('post-card-photo'); - const stamp = element.shadowRoot.querySelector('post-card-stamp'); - const postmark = element.shadowRoot.querySelector('post-card-postmark'); - - expect(photo.getAttribute('image')).to.equal(element.photoSrc); - expect(stamp.getAttribute('image')).to.equal(element.stampSrc); - expect(postmark.getAttribute('locations')).to.equal('Egypt'); + it("should pass correct attributes to sub-components", () => { + const photo = element.shadowRoot.querySelector("post-card-photo"); + const stamp = element.shadowRoot.querySelector("post-card-stamp"); + const postmark = element.shadowRoot.querySelector("post-card-postmark"); + + expect(photo.getAttribute("image")).to.equal(element.photoSrc); + expect(stamp.getAttribute("image")).to.equal(element.stampSrc); + expect(postmark.getAttribute("locations")).to.equal("Egypt"); }); }); - describe('Content Slots', () => { + describe("Content Slots", () => { it("renders an h3", () => { const h3 = element.shadowRoot.querySelector("h3"); expect(h3).to.exist; @@ -192,41 +194,42 @@ describe("PostCard", () => { expect(mess.textContent).to.equal("To make a baby...."); }); - it('should use fallback content when slots are empty', async () => { + it("should use fallback content when slots are empty", async () => { const emptyElement = await fixture(html``); - - const toContent = emptyElement.shadowRoot.querySelector('.toContent'); - const fromContent = emptyElement.shadowRoot.querySelector('.fromContent'); - const messageContent = emptyElement.shadowRoot.querySelector('.messageContent'); - + + const toContent = emptyElement.shadowRoot.querySelector(".toContent"); + const fromContent = emptyElement.shadowRoot.querySelector(".fromContent"); + const messageContent = + emptyElement.shadowRoot.querySelector(".messageContent"); + expect(toContent).to.exist; expect(fromContent).to.exist; expect(messageContent).to.exist; }); }); - describe('Responsive Design', () => { - it('should have responsive CSS custom properties', () => { + describe("Responsive Design", () => { + it("should have responsive CSS custom properties", () => { const styles = getComputedStyle(element); - expect(styles.getPropertyValue('--width-body')).to.not.be.empty; + expect(styles.getPropertyValue("--width-body")).to.not.be.empty; }); - it('should handle different screen sizes with transforms', () => { + it("should handle different screen sizes with transforms", () => { // Test that the component has styles defined const styles = element.constructor.styles; expect(styles).to.exist; - + // Convert CSSResult to string to check content - let stylesString = ''; + let stylesString = ""; if (Array.isArray(styles)) { - stylesString = styles.map(s => s.toString()).join(''); - } else if (styles && typeof styles.toString === 'function') { + stylesString = styles.map((s) => s.toString()).join(""); + } else if (styles && typeof styles.toString === "function") { stylesString = styles.toString(); } - + if (stylesString) { - expect(stylesString).to.include('@media'); - expect(stylesString).to.include('transform: scale'); + expect(stylesString).to.include("@media"); + expect(stylesString).to.include("transform: scale"); } else { // Fallback test - verify styles exist expect(styles).to.not.be.undefined; @@ -234,90 +237,92 @@ describe("PostCard", () => { }); }); - describe('I18n Support', () => { - it('should dispatch i18n manager registration event', () => { + describe("I18n Support", () => { + it("should dispatch i18n manager registration event", () => { // The event is dispatched in constructor, so we test its existence - expect(element.t).to.be.an('object'); - expect(element.t.label).to.be.a('string'); + expect(element.t).to.be.an("object"); + expect(element.t.label).to.be.a("string"); }); - it('should have translation properties', () => { - expect(element.t.label).to.equal('Post Card'); - expect(element.t.send).to.equal('To'); - expect(element.t.receive).to.equal('From'); + it("should have translation properties", () => { + expect(element.t.label).to.equal("Post Card"); + expect(element.t.send).to.equal("To"); + expect(element.t.receive).to.equal("From"); }); }); - describe('HAX Integration', () => { - it('should have haxProperties', () => { - expect(element.constructor.haxProperties).to.be.a('string'); - expect(element.constructor.haxProperties).to.include('haxProperties.json'); + describe("HAX Integration", () => { + it("should have haxProperties", () => { + expect(element.constructor.haxProperties).to.be.a("string"); + expect(element.constructor.haxProperties).to.include( + "haxProperties.json", + ); }); }); - describe('Edge Cases', () => { - it('should handle null/undefined property values', async () => { + describe("Edge Cases", () => { + it("should handle null/undefined property values", async () => { element.to = null; element.from = undefined; element.message = null; await element.updateComplete; - + expect(element.to).to.be.null; expect(element.from).to.be.undefined; expect(element.message).to.be.null; }); - it('should handle empty string values', async () => { - element.to = ''; - element.from = ''; - element.message = ''; + it("should handle empty string values", async () => { + element.to = ""; + element.from = ""; + element.message = ""; await element.updateComplete; - - expect(element.to).to.equal(''); - expect(element.from).to.equal(''); - expect(element.message).to.equal(''); + + expect(element.to).to.equal(""); + expect(element.from).to.equal(""); + expect(element.message).to.equal(""); }); - it('should handle long content gracefully', async () => { - const longText = 'A'.repeat(500); + it("should handle long content gracefully", async () => { + const longText = "A".repeat(500); element.to = longText; element.from = longText; element.message = longText; await element.updateComplete; - + expect(element.to).to.equal(longText); expect(element.from).to.equal(longText); expect(element.message).to.equal(longText); }); }); - describe('Multiple Instances', () => { - it('should support multiple post-card instances', async () => { + describe("Multiple Instances", () => { + it("should support multiple post-card instances", async () => { const element1 = await fixture(html` `); const element2 = await fixture(html` `); - - expect(element1.to).to.equal('Destination 1'); - expect(element1.from).to.equal('Origin 1'); - expect(element2.to).to.equal('Destination 2'); - expect(element2.from).to.equal('Origin 2'); + + expect(element1.to).to.equal("Destination 1"); + expect(element1.from).to.equal("Origin 1"); + expect(element2.to).to.equal("Destination 2"); + expect(element2.from).to.equal("Origin 2"); }); }); - describe('Performance', () => { - it('should handle rapid property updates efficiently', async () => { + describe("Performance", () => { + it("should handle rapid property updates efficiently", async () => { const startTime = performance.now(); - + for (let i = 0; i < 20; i++) { element.to = `Destination ${i}`; element.from = `Origin ${i}`; element.message = `Message ${i}`; await element.updateComplete; } - + const endTime = performance.now(); expect(endTime - startTime).to.be.lessThan(1000); // Should complete within 1 second }); @@ -336,61 +341,61 @@ describe("PostCardPostmark", () => { ); }); - describe('Component Structure', () => { - it('should be defined as a custom element', () => { - expect(customElements.get('post-card-postmark')).to.exist; - expect(element.tagName.toLowerCase()).to.equal('post-card-postmark'); + describe("Component Structure", () => { + it("should be defined as a custom element", () => { + expect(customElements.get("post-card-postmark")).to.exist; + expect(element.tagName.toLowerCase()).to.equal("post-card-postmark"); }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; - expect(element.constructor.name).to.include('PostCardPostmark'); + expect(element.constructor.name).to.include("PostCardPostmark"); }); - it('should have shadow DOM', () => { + it("should have shadow DOM", () => { expect(element.shadowRoot).to.exist; }); }); - describe('Property Handling', () => { - it('should handle locations property', () => { - expect(element.locations).to.equal('Europe'); + describe("Property Handling", () => { + it("should handle locations property", () => { + expect(element.locations).to.equal("Europe"); }); - it('should update locations property', async () => { - element.locations = 'Asia'; + it("should update locations property", async () => { + element.locations = "Asia"; await element.updateComplete; - expect(element.locations).to.equal('Asia'); + expect(element.locations).to.equal("Asia"); }); }); - describe('Template Rendering', () => { + describe("Template Rendering", () => { it("renders a location", async () => { const loco = element.shadowRoot.querySelector("p"); expect(loco).to.exist; // expect(loco.textContent).to.equal('Europe') }); - it('should render postmark visual elements', () => { + it("should render postmark visual elements", () => { // Check for typical postmark visual elements - const container = element.shadowRoot.querySelector('div'); + const container = element.shadowRoot.querySelector("div"); if (container) { expect(container).to.exist; } }); }); - describe('Multiple Instances', () => { - it('should support multiple postmark instances', async () => { + describe("Multiple Instances", () => { + it("should support multiple postmark instances", async () => { const element1 = await fixture( - html`` + html``, ); const element2 = await fixture( - html`` + html``, ); - - expect(element1.locations).to.equal('America'); - expect(element2.locations).to.equal('Africa'); + + expect(element1.locations).to.equal("America"); + expect(element2.locations).to.equal("Africa"); }); }); diff --git a/elements/pouch-db/.gitignore b/elements/pouch-db/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/pouch-db/.gitignore +++ b/elements/pouch-db/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/pouch-db/custom-elements.json b/elements/pouch-db/custom-elements.json deleted file mode 100755 index 343270d8c1..0000000000 --- a/elements/pouch-db/custom-elements.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "pouch-db.js", - "declarations": [ - { - "kind": "variable", - "name": "PouchDBElement" - }, - { - "kind": "class", - "description": "`pouch-db`\n`read and write localized data elements`", - "name": "PouchDb", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true - }, - { - "kind": "method", - "name": "userEngagmentFunction", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "getDataFunction", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "field", - "name": "windowControllers", - "default": "new AbortController()" - }, - { - "kind": "field", - "name": "type", - "type": { - "text": "string" - }, - "default": "\"xapi\"" - } - ], - "superclass": { - "name": "HTMLElement" - }, - "tagName": "pouch-db", - "customElement": true - } - ], - "exports": [ - { - "kind": "js", - "name": "PouchDBElement", - "declaration": { - "name": "PouchDBElement", - "module": "pouch-db.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "PouchDb", - "module": "pouch-db.js" - } - }, - { - "kind": "js", - "name": "PouchDb", - "declaration": { - "name": "PouchDb", - "module": "pouch-db.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/pouchdb.min.js", - "declarations": [], - "exports": [] - } - ] -} diff --git a/elements/pouch-db/demo/index.html b/elements/pouch-db/demo/index.html index 6e613df32b..bda3803acd 100644 --- a/elements/pouch-db/demo/index.html +++ b/elements/pouch-db/demo/index.html @@ -9,7 +9,7 @@ - + + - + diff --git a/elements/pouch-db/package.json b/elements/pouch-db/package.json old mode 100755 new mode 100644 index d7b450fbd5..e9599d58cc --- a/elements/pouch-db/package.json +++ b/elements/pouch-db/package.json @@ -46,10 +46,7 @@ "@haxtheweb/deduping-fix": "^11.0.0", "@haxtheweb/multiple-choice": "^11.0.5", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/pouch-db/test/pouch-db.test.js b/elements/pouch-db/test/pouch-db.test.js index fe7adfe571..bb635016f4 100644 --- a/elements/pouch-db/test/pouch-db.test.js +++ b/elements/pouch-db/test/pouch-db.test.js @@ -9,71 +9,73 @@ describe("pouch-db test", () => { element = await fixture(html` `); }); - describe('Component Structure', () => { - it('should be defined as a custom element', () => { - expect(customElements.get('pouch-db')).to.exist; - expect(element.tagName.toLowerCase()).to.equal('pouch-db'); + describe("Component Structure", () => { + it("should be defined as a custom element", () => { + expect(customElements.get("pouch-db")).to.exist; + expect(element.tagName.toLowerCase()).to.equal("pouch-db"); }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; - expect(element.constructor.name).to.equal('PouchDb'); + expect(element.constructor.name).to.equal("PouchDb"); }); - it('should have correct tag property', () => { - expect(element.constructor.tag).to.equal('pouch-db'); + it("should have correct tag property", () => { + expect(element.constructor.tag).to.equal("pouch-db"); }); - it('should extend HTMLElement', () => { + it("should extend HTMLElement", () => { expect(element instanceof HTMLElement).to.be.true; }); - it('should not have shadow DOM', () => { + it("should not have shadow DOM", () => { expect(element.shadowRoot).to.be.null; }); }); - describe('Singleton Pattern', () => { - it('should have global PouchDb namespace', () => { + describe("Singleton Pattern", () => { + it("should have global PouchDb namespace", () => { expect(globalThis.PouchDb).to.exist; - expect(typeof globalThis.PouchDb.requestAvailability).to.equal('function'); + expect(typeof globalThis.PouchDb.requestAvailability).to.equal( + "function", + ); }); - it('should return same instance from requestAvailability', () => { + it("should return same instance from requestAvailability", () => { const instance1 = globalThis.PouchDb.requestAvailability(); const instance2 = globalThis.PouchDb.requestAvailability(); expect(instance1).to.equal(instance2); }); - it('should have singleton instance available as export', () => { + it("should have singleton instance available as export", () => { expect(PouchDBElement).to.exist; - expect(PouchDBElement.tagName.toLowerCase()).to.equal('pouch-db'); + expect(PouchDBElement.tagName.toLowerCase()).to.equal("pouch-db"); }); - it('should maintain single instance across multiple requests', () => { + it("should maintain single instance across multiple requests", () => { const instances = []; for (let i = 0; i < 5; i++) { instances.push(globalThis.PouchDb.requestAvailability()); } - + // All instances should be the same object - instances.forEach(instance => { + instances.forEach((instance) => { expect(instance).to.equal(instances[0]); }); }); }); - describe('Initialization', () => { - it('should have default type property', () => { - expect(element.type).to.equal('xapi'); + describe("Initialization", () => { + it("should have default type property", () => { + expect(element.type).to.equal("xapi"); }); - it('should have windowControllers AbortController', () => { + it("should have windowControllers AbortController", () => { expect(element.windowControllers).to.exist; expect(element.windowControllers instanceof AbortController).to.be.true; }); - it('should initialize database asynchronously', (done) => { + it("should initialize database asynchronously", (done) => { // Wait a bit for the dynamic import to complete setTimeout(() => { expect(element.db).to.exist; @@ -82,49 +84,49 @@ describe("pouch-db test", () => { }); }); - describe('Event Listeners', () => { - it('should bind to user-engagement events', () => { - expect(typeof element.userEngagmentFunction).to.equal('function'); + describe("Event Listeners", () => { + it("should bind to user-engagement events", () => { + expect(typeof element.userEngagmentFunction).to.equal("function"); }); - it('should bind to pouch-db-get-data events', () => { - expect(typeof element.getDataFunction).to.equal('function'); + it("should bind to pouch-db-get-data events", () => { + expect(typeof element.getDataFunction).to.equal("function"); }); - it('should handle user-engagement events', () => { + it("should handle user-engagement events", () => { const mockEvent = { detail: { - activityDisplay: 'answered', - objectName: 'Test Quiz', - resultSuccess: true + activityDisplay: "answered", + objectName: "Test Quiz", + resultSuccess: true, }, - target: { tagName: 'MULTIPLE-CHOICE' } + target: { tagName: "MULTIPLE-CHOICE" }, }; expect(() => element.userEngagmentFunction(mockEvent)).to.not.throw; }); - it('should handle pouch-db-get-data events', () => { + it("should handle pouch-db-get-data events", () => { const mockEvent = { detail: { - queryRequest: 'all-quizzes' + queryRequest: "all-quizzes", }, - target: { tagName: 'TEST-ELEMENT' } + target: { tagName: "TEST-ELEMENT" }, }; expect(() => element.getDataFunction(mockEvent)).to.not.throw; }); }); - describe('User Engagement Processing', () => { - it('should process MULTIPLE-CHOICE events', () => { + describe("User Engagement Processing", () => { + it("should process MULTIPLE-CHOICE events", () => { const mockEvent = { detail: { - activityDisplay: 'answered', - objectName: 'Test Quiz', - resultSuccess: true + activityDisplay: "answered", + objectName: "Test Quiz", + resultSuccess: true, }, - target: { tagName: 'MULTIPLE-CHOICE' } + target: { tagName: "MULTIPLE-CHOICE" }, }; // Mock console.log to capture output @@ -135,163 +137,169 @@ describe("pouch-db test", () => { }; element.userEngagmentFunction(mockEvent); - + expect(loggedData).to.exist; - expect(loggedData._id).to.be.a('string'); - expect(loggedData.title).to.be.a('string'); + expect(loggedData._id).to.be.a("string"); + expect(loggedData.title).to.be.a("string"); expect(loggedData.completed).to.be.false; // Restore console.log console.log = originalLog; }); - it('should handle unknown event types gracefully', () => { + it("should handle unknown event types gracefully", () => { const mockEvent = { detail: { - activityDisplay: 'unknown', - objectName: 'Test', - resultSuccess: false + activityDisplay: "unknown", + objectName: "Test", + resultSuccess: false, }, - target: { tagName: 'UNKNOWN-ELEMENT' } + target: { tagName: "UNKNOWN-ELEMENT" }, }; expect(() => element.userEngagmentFunction(mockEvent)).to.not.throw; }); }); - describe('Data Query Processing', () => { - it('should handle all-quizzes query request', () => { + describe("Data Query Processing", () => { + it("should handle all-quizzes query request", () => { const mockEvent = { detail: { - queryRequest: 'all-quizzes' + queryRequest: "all-quizzes", }, - target: { tagName: 'TEST-ELEMENT' } + target: { tagName: "TEST-ELEMENT" }, }; expect(() => element.getDataFunction(mockEvent)).to.not.throw; }); - it('should handle single-quiz query request', () => { + it("should handle single-quiz query request", () => { const mockEvent = { detail: { - queryRequest: 'single-quiz', - objectName: 'Quiz1' + queryRequest: "single-quiz", + objectName: "Quiz1", }, - target: { tagName: 'TEST-ELEMENT' } + target: { tagName: "TEST-ELEMENT" }, }; expect(() => element.getDataFunction(mockEvent)).to.not.throw; }); - it('should handle future-query request', () => { + it("should handle future-query request", () => { const mockEvent = { detail: { - queryRequest: 'future-query', - activityDisplay: 'completed', - objectName: 'Advanced Quiz', + queryRequest: "future-query", + activityDisplay: "completed", + objectName: "Advanced Quiz", resultSuccess: true, - resultCompletion: true + resultCompletion: true, }, - target: { tagName: 'TEST-ELEMENT' } + target: { tagName: "TEST-ELEMENT" }, }; expect(() => element.getDataFunction(mockEvent)).to.not.throw; }); - it('should handle unknown query request', () => { + it("should handle unknown query request", () => { const mockEvent = { detail: { - queryRequest: 'unknown-query' + queryRequest: "unknown-query", }, - target: { tagName: 'TEST-ELEMENT' } + target: { tagName: "TEST-ELEMENT" }, }; expect(() => element.getDataFunction(mockEvent)).to.not.throw; }); }); - describe('Event Dispatching', () => { - it('should dispatch pouch-db-show-data event', (done) => { + describe("Event Dispatching", () => { + it("should dispatch pouch-db-show-data event", (done) => { const mockQueryData = { - labels: ['Quiz1', 'Quiz2'], - series: [[1, 2]] + labels: ["Quiz1", "Quiz2"], + series: [[1, 2]], }; // Listen for the dispatched event - globalThis.addEventListener('pouch-db-show-data', (event) => { - expect(event.detail).to.exist; - expect(event.bubbles).to.be.true; - expect(event.composed).to.be.true; - expect(event.cancelable).to.be.false; - done(); - }, { once: true }); + globalThis.addEventListener( + "pouch-db-show-data", + (event) => { + expect(event.detail).to.exist; + expect(event.bubbles).to.be.true; + expect(event.composed).to.be.true; + expect(event.cancelable).to.be.false; + done(); + }, + { once: true }, + ); // Trigger a data query that should dispatch the event const mockEvent = { detail: { - queryRequest: 'all-quizzes' + queryRequest: "all-quizzes", }, - target: { tagName: 'TEST-ELEMENT' } + target: { tagName: "TEST-ELEMENT" }, }; element.getDataFunction(mockEvent); }); }); - describe('Lifecycle Management', () => { - it('should setup event listeners on connect', () => { + describe("Lifecycle Management", () => { + it("should setup event listeners on connect", () => { const spy = { called: false, - addEventListener: () => { spy.called = true; } + addEventListener: () => { + spy.called = true; + }, }; - + const originalAddEventListener = globalThis.addEventListener; globalThis.addEventListener = spy.addEventListener; - + element.connectedCallback(); expect(spy.called).to.be.true; - + globalThis.addEventListener = originalAddEventListener; }); - it('should cleanup on disconnect', () => { + it("should cleanup on disconnect", () => { const originalAbort = element.windowControllers.abort; let abortCalled = false; element.windowControllers.abort = () => { abortCalled = true; originalAbort.call(element.windowControllers); }; - + element.disconnectedCallback(); expect(abortCalled).to.be.true; }); }); - describe('Edge Cases', () => { - it('should handle events with missing detail', () => { + describe("Edge Cases", () => { + it("should handle events with missing detail", () => { const mockEvent = { - target: { tagName: 'MULTIPLE-CHOICE' } + target: { tagName: "MULTIPLE-CHOICE" }, }; expect(() => element.userEngagmentFunction(mockEvent)).to.not.throw; }); - it('should handle events with null target', () => { + it("should handle events with null target", () => { const mockEvent = { - detail: { queryRequest: 'all-quizzes' }, - target: null + detail: { queryRequest: "all-quizzes" }, + target: null, }; expect(() => element.getDataFunction(mockEvent)).to.not.throw; }); - it('should handle malformed event data gracefully', () => { + it("should handle malformed event data gracefully", () => { const mockEvent = { detail: { - invalidProperty: 'test', - nested: { deeply: { invalid: true } } + invalidProperty: "test", + nested: { deeply: { invalid: true } }, }, - target: { tagName: 'INVALID-ELEMENT' } + target: { tagName: "INVALID-ELEMENT" }, }; expect(() => element.userEngagmentFunction(mockEvent)).to.not.throw; @@ -299,32 +307,32 @@ describe("pouch-db test", () => { }); }); - describe('Database Operations', () => { - it('should have xAPI statement structure', () => { + describe("Database Operations", () => { + it("should have xAPI statement structure", () => { const mockEvent = { detail: { - activityDisplay: 'answered', - objectName: 'Test Quiz', - resultSuccess: true + activityDisplay: "answered", + objectName: "Test Quiz", + resultSuccess: true, }, - target: { tagName: 'MULTIPLE-CHOICE' } + target: { tagName: "MULTIPLE-CHOICE" }, }; const originalLog = console.log; let xapiStatement = null; console.log = (data) => { - if (data._id && data.title && typeof data.completed !== 'undefined') { + if (data._id && data.title && typeof data.completed !== "undefined") { xapiStatement = data; } }; element.userEngagmentFunction(mockEvent); - + if (xapiStatement) { - expect(xapiStatement._id).to.be.a('string'); - expect(xapiStatement.title).to.be.a('string'); - expect(xapiStatement.completed).to.be.a('boolean'); - + expect(xapiStatement._id).to.be.a("string"); + expect(xapiStatement.title).to.be.a("string"); + expect(xapiStatement.completed).to.be.a("boolean"); + // Parse the title to check xAPI structure const parsedTitle = JSON.parse(xapiStatement.title); expect(parsedTitle.actor).to.exist; @@ -337,35 +345,35 @@ describe("pouch-db test", () => { }); }); - describe('Multiple Instances Behavior', () => { - it('should maintain singleton behavior with multiple fixture instances', async () => { + describe("Multiple Instances Behavior", () => { + it("should maintain singleton behavior with multiple fixture instances", async () => { const element1 = await fixture(html``); const element2 = await fixture(html``); - + // Both should reference the same singleton through global availability const singleton1 = globalThis.PouchDb.requestAvailability(); const singleton2 = globalThis.PouchDb.requestAvailability(); - + expect(singleton1).to.equal(singleton2); expect(element1.constructor).to.equal(element2.constructor); }); }); - describe('Performance', () => { - it('should handle rapid event processing efficiently', () => { + describe("Performance", () => { + it("should handle rapid event processing efficiently", () => { const startTime = performance.now(); - + for (let i = 0; i < 50; i++) { const mockEvent = { detail: { - queryRequest: 'all-quizzes' + queryRequest: "all-quizzes", }, - target: { tagName: `TEST-ELEMENT-${i}` } + target: { tagName: `TEST-ELEMENT-${i}` }, }; - + element.getDataFunction(mockEvent); } - + const endTime = performance.now(); expect(endTime - startTime).to.be.lessThan(1000); // Should complete within 1 second }); diff --git a/elements/product-card/.gitignore b/elements/product-card/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/product-card/.gitignore +++ b/elements/product-card/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/product-card/custom-elements.json b/elements/product-card/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/product-card/demo/hax.html b/elements/product-card/demo/hax.html index cd6f94ba4a..96160831fd 100644 --- a/elements/product-card/demo/hax.html +++ b/elements/product-card/demo/hax.html @@ -4,7 +4,6 @@ HAX registry builder demo - diff --git a/elements/product-card/demo/index.html b/elements/product-card/demo/index.html index 548983daaa..0cc6734352 100644 --- a/elements/product-card/demo/index.html +++ b/elements/product-card/demo/index.html @@ -4,12 +4,11 @@ HAX registry builder demo - - + + - + diff --git a/elements/product-card/package.json b/elements/product-card/package.json old mode 100755 new mode 100644 index 7643092c3d..e8cc3f0b51 --- a/elements/product-card/package.json +++ b/elements/product-card/package.json @@ -46,7 +46,7 @@ "@haxtheweb/grid-plate": "^11.0.5", "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/utils": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", @@ -81,10 +81,7 @@ "@haxtheweb/wikipedia-query": "^11.0.5", "@lrnwebcomponents/lrndesign-gallery": "^7.0.11", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/product-card/test/product-card.test.js b/elements/product-card/test/product-card.test.js index 08c3a00735..fd1329d0f7 100644 --- a/elements/product-card/test/product-card.test.js +++ b/elements/product-card/test/product-card.test.js @@ -12,441 +12,467 @@ describe("product-card test", () => { `); }); - describe('Component Structure', () => { - it('should be defined as a custom element', () => { - expect(customElements.get('product-card')).to.exist; - expect(element.tagName.toLowerCase()).to.equal('product-card'); + describe("Component Structure", () => { + it("should be defined as a custom element", () => { + expect(customElements.get("product-card")).to.exist; + expect(element.tagName.toLowerCase()).to.equal("product-card"); }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; expect(element instanceof ProductCard).to.be.true; }); - it('should have correct tag property', () => { - expect(ProductCard.tag).to.equal('product-card'); + it("should have correct tag property", () => { + expect(ProductCard.tag).to.equal("product-card"); }); - it('should extend SimpleColors', () => { - expect(element.constructor.name).to.equal('ProductCard'); + it("should extend SimpleColors", () => { + expect(element.constructor.name).to.equal("ProductCard"); }); - it('should have shadow DOM', () => { + it("should have shadow DOM", () => { expect(element.shadowRoot).to.exist; }); }); - describe('Default Properties', () => { - it('should have default disabled state', () => { + describe("Default Properties", () => { + it("should have default disabled state", () => { expect(element.disabled).to.be.false; }); - it('should have default heading property', () => { + it("should have default heading property", () => { expect(element.heading).to.be.undefined; }); - it('should have default subheading property', () => { + it("should have default subheading property", () => { expect(element.subheading).to.be.undefined; }); - it('should have default icon property', () => { + it("should have default icon property", () => { expect(element.icon).to.be.undefined; }); - it('should have default hasDemo property', () => { + it("should have default hasDemo property", () => { expect(element.hasDemo).to.be.undefined; }); }); - describe('Property Updates', () => { - it('should update disabled property', async () => { + describe("Property Updates", () => { + it("should update disabled property", async () => { element.disabled = true; await element.updateComplete; expect(element.disabled).to.be.true; - expect(element.hasAttribute('disabled')).to.be.true; + expect(element.hasAttribute("disabled")).to.be.true; }); - it('should update heading property', async () => { - element.heading = 'Test Heading'; + it("should update heading property", async () => { + element.heading = "Test Heading"; await element.updateComplete; - expect(element.heading).to.equal('Test Heading'); + expect(element.heading).to.equal("Test Heading"); }); - it('should update subheading property', async () => { - element.subheading = 'Test Subheading'; + it("should update subheading property", async () => { + element.subheading = "Test Subheading"; await element.updateComplete; - expect(element.subheading).to.equal('Test Subheading'); + expect(element.subheading).to.equal("Test Subheading"); }); - it('should update icon property', async () => { - element.icon = 'star'; + it("should update icon property", async () => { + element.icon = "star"; await element.updateComplete; - expect(element.icon).to.equal('star'); + expect(element.icon).to.equal("star"); }); - it('should update hasDemo property', async () => { + it("should update hasDemo property", async () => { element.hasDemo = true; await element.updateComplete; expect(element.hasDemo).to.be.true; }); }); - describe('Template Rendering', () => { - it('should render accent-card component', async () => { + describe("Template Rendering", () => { + it("should render accent-card component", async () => { await element.updateComplete; - const accentCard = element.shadowRoot.querySelector('accent-card'); + const accentCard = element.shadowRoot.querySelector("accent-card"); expect(accentCard).to.exist; }); - it('should render a11y-collapse-group', async () => { + it("should render a11y-collapse-group", async () => { await element.updateComplete; - const collapseGroup = element.shadowRoot.querySelector('a11y-collapse-group'); + const collapseGroup = element.shadowRoot.querySelector( + "a11y-collapse-group", + ); expect(collapseGroup).to.exist; }); - it('should render multiple a11y-collapse elements', async () => { + it("should render multiple a11y-collapse elements", async () => { await element.updateComplete; - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); expect(collapses).to.have.length(2); }); - it('should render heading when provided', async () => { - element.heading = 'Product Title'; + it("should render heading when provided", async () => { + element.heading = "Product Title"; await element.updateComplete; - - const headingSlot = element.shadowRoot.querySelector('div[slot="heading"]'); + + const headingSlot = element.shadowRoot.querySelector( + 'div[slot="heading"]', + ); expect(headingSlot).to.exist; - expect(headingSlot.textContent.trim()).to.include('Product Title'); + expect(headingSlot.textContent.trim()).to.include("Product Title"); }); - it('should render subheading when provided', async () => { - element.subheading = 'Product Subtitle'; + it("should render subheading when provided", async () => { + element.subheading = "Product Subtitle"; await element.updateComplete; - - const subheadingSlot = element.shadowRoot.querySelector('div[slot="subheading"]'); + + const subheadingSlot = element.shadowRoot.querySelector( + 'div[slot="subheading"]', + ); expect(subheadingSlot).to.exist; - expect(subheadingSlot.textContent.trim()).to.include('Product Subtitle'); + expect(subheadingSlot.textContent.trim()).to.include("Product Subtitle"); }); - it('should render icon when provided', async () => { - element.icon = 'star'; + it("should render icon when provided", async () => { + element.icon = "star"; await element.updateComplete; - - const iconElement = element.shadowRoot.querySelector('simple-icon'); + + const iconElement = element.shadowRoot.querySelector("simple-icon"); expect(iconElement).to.exist; - expect(iconElement.getAttribute('icon')).to.equal('star'); + expect(iconElement.getAttribute("icon")).to.equal("star"); }); - it('should not render icon when not provided', async () => { + it("should not render icon when not provided", async () => { await element.updateComplete; - const iconElement = element.shadowRoot.querySelector('simple-icon'); + const iconElement = element.shadowRoot.querySelector("simple-icon"); expect(iconElement).to.not.exist; }); }); - describe('Disabled State', () => { - it('should apply disabled attribute to accent-card when disabled', async () => { + describe("Disabled State", () => { + it("should apply disabled attribute to accent-card when disabled", async () => { element.disabled = true; await element.updateComplete; - - const accentCard = element.shadowRoot.querySelector('accent-card'); - expect(accentCard.hasAttribute('flat')).to.be.true; + + const accentCard = element.shadowRoot.querySelector("accent-card"); + expect(accentCard.hasAttribute("flat")).to.be.true; }); - it('should change accent color to grey when disabled', async () => { + it("should change accent color to grey when disabled", async () => { element.disabled = true; await element.updateComplete; - - const accentCard = element.shadowRoot.querySelector('accent-card'); - expect(accentCard.getAttribute('accent-color')).to.equal('grey'); + + const accentCard = element.shadowRoot.querySelector("accent-card"); + expect(accentCard.getAttribute("accent-color")).to.equal("grey"); }); - it('should disable collapse elements when disabled', async () => { + it("should disable collapse elements when disabled", async () => { element.disabled = true; await element.updateComplete; - - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); - expect(collapses[0].hasAttribute('disabled')).to.be.true; // Details collapse + + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); + expect(collapses[0].hasAttribute("disabled")).to.be.true; // Details collapse // Demo collapse: depends on both disabled AND hasDemo - when hasDemo is undefined, it becomes enabled // This shows that Lit evaluates !undefined as true, but disabled || !undefined might not work as expected }); }); - describe('Demo Functionality', () => { - it('should evaluate demo collapse expression correctly when hasDemo is false', async () => { + describe("Demo Functionality", () => { + it("should evaluate demo collapse expression correctly when hasDemo is false", async () => { element.hasDemo = false; element.disabled = false; await element.updateComplete; - + // The expression evaluates correctly but Lit doesn't set the DOM attribute expect(element.disabled || !element.hasDemo).to.be.true; - - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); + + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); const demoCollapse = collapses[1]; // Second collapse is for demo // Note: This is the actual behavior - DOM attribute doesn't match the expression - expect(demoCollapse.hasAttribute('disabled')).to.be.false; + expect(demoCollapse.hasAttribute("disabled")).to.be.false; }); - it('should enable demo collapse when hasDemo is true', async () => { + it("should enable demo collapse when hasDemo is true", async () => { element.hasDemo = true; element.disabled = false; await element.updateComplete; - + expect(element.disabled || !element.hasDemo).to.be.false; - - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); + + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); const demoCollapse = collapses[1]; // Second collapse is for demo - expect(demoCollapse.hasAttribute('disabled')).to.be.false; + expect(demoCollapse.hasAttribute("disabled")).to.be.false; }); - - it('should understand actual behavior of disabled and hasDemo combination', async () => { + + it("should understand actual behavior of disabled and hasDemo combination", async () => { // Test the exact failing case - the boolean expression evaluates correctly // but the DOM attribute is not set, indicating a Lit binding issue element.hasDemo = true; element.disabled = true; await element.updateComplete; - + // The JavaScript expression works correctly expect(element.disabled || !element.hasDemo).to.be.true; - - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); + + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); const demoCollapse = collapses[1]; - + // However, the DOM attribute is not set (actual behavior observed) - expect(demoCollapse.hasAttribute('disabled')).to.be.false; + expect(demoCollapse.hasAttribute("disabled")).to.be.false; }); - - it('should understand disabled state behavior when disabled and hasDemo is undefined', async () => { + + it("should understand disabled state behavior when disabled and hasDemo is undefined", async () => { element.hasDemo = undefined; // default state element.disabled = true; await element.updateComplete; - + // The expression disabled || !hasDemo evaluates to true || true = true expect(element.disabled || !element.hasDemo).to.be.true; - - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); + + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); const demoCollapse = collapses[1]; // Second collapse is for demo // But the DOM attribute doesn't get set due to Lit's boolean binding behavior - expect(demoCollapse.hasAttribute('disabled')).to.be.false; + expect(demoCollapse.hasAttribute("disabled")).to.be.false; }); }); - describe('Event Handling', () => { - it('should dispatch product-card-demo-show event on demo collapse expand', async () => { + describe("Event Handling", () => { + it("should dispatch product-card-demo-show event on demo collapse expand", async () => { let eventFired = false; let eventDetail = null; - - element.addEventListener('product-card-demo-show', (e) => { + + element.addEventListener("product-card-demo-show", (e) => { eventFired = true; eventDetail = e.detail; }); - + const mockEvent = { - detail: { expanded: true } + detail: { expanded: true }, }; - + element.__demoCollapseStatusChange(mockEvent); - + expect(eventFired).to.be.true; expect(eventDetail).to.deep.equal({ expanded: true }); }); - it('should dispatch product-card-demo-show event on demo collapse collapse', async () => { + it("should dispatch product-card-demo-show event on demo collapse collapse", async () => { let eventFired = false; let eventDetail = null; - - element.addEventListener('product-card-demo-show', (e) => { + + element.addEventListener("product-card-demo-show", (e) => { eventFired = true; eventDetail = e.detail; }); - + const mockEvent = { - detail: { expanded: false } + detail: { expanded: false }, }; - + element.__demoCollapseStatusChange(mockEvent); - + expect(eventFired).to.be.true; expect(eventDetail).to.deep.equal({ expanded: false }); }); }); - describe('Slot Content', () => { - it('should support card-header slot', async () => { + describe("Slot Content", () => { + it("should support card-header slot", async () => { const slottedElement = await fixture(html` Header Content `); - + await slottedElement.updateComplete; - const headerSlot = slottedElement.shadowRoot.querySelector('slot[name="card-header"]'); + const headerSlot = slottedElement.shadowRoot.querySelector( + 'slot[name="card-header"]', + ); expect(headerSlot).to.exist; }); - it('should support card-subheader slot', async () => { + it("should support card-subheader slot", async () => { const slottedElement = await fixture(html` Subheader Content `); - + await slottedElement.updateComplete; - const subheaderSlot = slottedElement.shadowRoot.querySelector('slot[name="card-subheader"]'); + const subheaderSlot = slottedElement.shadowRoot.querySelector( + 'slot[name="card-subheader"]', + ); expect(subheaderSlot).to.exist; }); - it('should support default slot for content', async () => { + it("should support default slot for content", async () => { const slottedElement = await fixture(html`
    Main Content
    `); - + await slottedElement.updateComplete; - const defaultSlot = slottedElement.shadowRoot.querySelector('div[slot="content"] slot:not([name])'); + const defaultSlot = slottedElement.shadowRoot.querySelector( + 'div[slot="content"] slot:not([name])', + ); expect(defaultSlot).to.exist; }); - it('should support details-collapse slots', async () => { + it("should support details-collapse slots", async () => { const slottedElement = await fixture(html` Details Header
    Details Content
    `); - + await slottedElement.updateComplete; - const detailsHeaderSlot = slottedElement.shadowRoot.querySelector('slot[name="details-collapse-header"]'); - const detailsContentSlot = slottedElement.shadowRoot.querySelector('slot[name="details-collapse-content"]'); + const detailsHeaderSlot = slottedElement.shadowRoot.querySelector( + 'slot[name="details-collapse-header"]', + ); + const detailsContentSlot = slottedElement.shadowRoot.querySelector( + 'slot[name="details-collapse-content"]', + ); expect(detailsHeaderSlot).to.exist; expect(detailsContentSlot).to.exist; }); - it('should support demo-collapse slots', async () => { + it("should support demo-collapse slots", async () => { const slottedElement = await fixture(html` Demo Header
    Demo Content
    `); - + await slottedElement.updateComplete; - const demoHeaderSlot = slottedElement.shadowRoot.querySelector('slot[name="demo-collapse-header"]'); - const demoContentSlot = slottedElement.shadowRoot.querySelector('slot[name="demo-collapse-content"]'); + const demoHeaderSlot = slottedElement.shadowRoot.querySelector( + 'slot[name="demo-collapse-header"]', + ); + const demoContentSlot = slottedElement.shadowRoot.querySelector( + 'slot[name="demo-collapse-content"]', + ); expect(demoHeaderSlot).to.exist; expect(demoContentSlot).to.exist; }); }); - describe('SimpleColors Integration', () => { - it('should inherit SimpleColors properties', () => { + describe("SimpleColors Integration", () => { + it("should inherit SimpleColors properties", () => { expect(element.accentColor).to.exist; }); - it('should pass accent color to accent-card when not disabled', async () => { - element.accentColor = 'red'; + it("should pass accent color to accent-card when not disabled", async () => { + element.accentColor = "red"; element.disabled = false; await element.updateComplete; - - const accentCard = element.shadowRoot.querySelector('accent-card'); - expect(accentCard.getAttribute('accent-color')).to.equal('red'); + + const accentCard = element.shadowRoot.querySelector("accent-card"); + expect(accentCard.getAttribute("accent-color")).to.equal("red"); }); - it('should use grey accent color when disabled regardless of accentColor', async () => { - element.accentColor = 'blue'; + it("should use grey accent color when disabled regardless of accentColor", async () => { + element.accentColor = "blue"; element.disabled = true; await element.updateComplete; - - const accentCard = element.shadowRoot.querySelector('accent-card'); - expect(accentCard.getAttribute('accent-color')).to.equal('grey'); + + const accentCard = element.shadowRoot.querySelector("accent-card"); + expect(accentCard.getAttribute("accent-color")).to.equal("grey"); }); }); - describe('Complex Scenarios', () => { - it('should handle all properties set together', async () => { - element.heading = 'Complete Product'; - element.subheading = 'Full Description'; - element.icon = 'star'; + describe("Complex Scenarios", () => { + it("should handle all properties set together", async () => { + element.heading = "Complete Product"; + element.subheading = "Full Description"; + element.icon = "star"; element.hasDemo = true; - element.accentColor = 'purple'; + element.accentColor = "purple"; element.disabled = false; - + await element.updateComplete; - - const headingDiv = element.shadowRoot.querySelector('div[slot="heading"]'); - const subheadingDiv = element.shadowRoot.querySelector('div[slot="subheading"]'); - const iconElement = element.shadowRoot.querySelector('simple-icon'); - const accentCard = element.shadowRoot.querySelector('accent-card'); - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); - - expect(headingDiv.textContent.trim()).to.include('Complete Product'); - expect(subheadingDiv.textContent.trim()).to.include('Full Description'); - expect(iconElement.getAttribute('icon')).to.equal('star'); - expect(accentCard.getAttribute('accent-color')).to.equal('purple'); - expect(collapses[1].hasAttribute('disabled')).to.be.false; - }); - - it('should handle disabled state with demo', async () => { + + const headingDiv = element.shadowRoot.querySelector( + 'div[slot="heading"]', + ); + const subheadingDiv = element.shadowRoot.querySelector( + 'div[slot="subheading"]', + ); + const iconElement = element.shadowRoot.querySelector("simple-icon"); + const accentCard = element.shadowRoot.querySelector("accent-card"); + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); + + expect(headingDiv.textContent.trim()).to.include("Complete Product"); + expect(subheadingDiv.textContent.trim()).to.include("Full Description"); + expect(iconElement.getAttribute("icon")).to.equal("star"); + expect(accentCard.getAttribute("accent-color")).to.equal("purple"); + expect(collapses[1].hasAttribute("disabled")).to.be.false; + }); + + it("should handle disabled state with demo", async () => { element.hasDemo = true; element.disabled = true; - + await element.updateComplete; - - const accentCard = element.shadowRoot.querySelector('accent-card'); - const collapses = element.shadowRoot.querySelectorAll('a11y-collapse'); - - expect(accentCard.getAttribute('accent-color')).to.equal('grey'); - expect(accentCard.hasAttribute('flat')).to.be.true; - expect(collapses[0].hasAttribute('disabled')).to.be.true; // Details collapse - expect(collapses[1].hasAttribute('disabled')).to.be.false; // Demo collapse - matches actual behavior + + const accentCard = element.shadowRoot.querySelector("accent-card"); + const collapses = element.shadowRoot.querySelectorAll("a11y-collapse"); + + expect(accentCard.getAttribute("accent-color")).to.equal("grey"); + expect(accentCard.hasAttribute("flat")).to.be.true; + expect(collapses[0].hasAttribute("disabled")).to.be.true; // Details collapse + expect(collapses[1].hasAttribute("disabled")).to.be.false; // Demo collapse - matches actual behavior }); }); - describe('Edge Cases', () => { - it('should handle empty heading gracefully', async () => { - element.heading = ''; + describe("Edge Cases", () => { + it("should handle empty heading gracefully", async () => { + element.heading = ""; await element.updateComplete; - - const headingDiv = element.shadowRoot.querySelector('div[slot="heading"]'); + + const headingDiv = element.shadowRoot.querySelector( + 'div[slot="heading"]', + ); expect(headingDiv).to.exist; expect(headingDiv.textContent.trim()).to.be.empty; }); - it('should handle null icon gracefully', async () => { + it("should handle null icon gracefully", async () => { element.icon = null; await element.updateComplete; - - const iconElement = element.shadowRoot.querySelector('simple-icon'); + + const iconElement = element.shadowRoot.querySelector("simple-icon"); expect(iconElement).to.not.exist; }); - it('should handle undefined properties gracefully', async () => { + it("should handle undefined properties gracefully", async () => { element.heading = undefined; element.subheading = undefined; element.icon = undefined; element.hasDemo = undefined; - + await element.updateComplete; - + expect(() => element.render()).to.not.throw; }); }); - describe('Multiple Instances', () => { - it('should support multiple independent instances', async () => { + describe("Multiple Instances", () => { + it("should support multiple independent instances", async () => { const element1 = await fixture(html` `); const element2 = await fixture(html` `); - + await element1.updateComplete; await element2.updateComplete; - - expect(element1.heading).to.equal('Product 1'); - expect(element2.heading).to.equal('Product 2'); + + expect(element1.heading).to.equal("Product 1"); + expect(element2.heading).to.equal("Product 2"); expect(element1.disabled).to.be.true; expect(element2.disabled).to.be.false; expect(element1.hasDemo).to.be.undefined; @@ -454,23 +480,22 @@ describe("product-card test", () => { }); }); - describe('Performance', () => { - it('should handle rapid property changes efficiently', async () => { + describe("Performance", () => { + it("should handle rapid property changes efficiently", async () => { const startTime = performance.now(); - + for (let i = 0; i < 50; i++) { element.heading = `Product ${i}`; element.subheading = `Description ${i}`; element.disabled = i % 2 === 0; await element.updateComplete; } - + const endTime = performance.now(); expect(endTime - startTime).to.be.lessThan(5000); // Should complete within 5 seconds }); }); - it("passes the a11y audit", async () => { await expect(element).shadowDom.to.be.accessible(); }); diff --git a/elements/product-glance/.gitignore b/elements/product-glance/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/product-glance/.gitignore +++ b/elements/product-glance/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/product-glance/custom-elements.json b/elements/product-glance/custom-elements.json deleted file mode 100644 index 13fcc4791d..0000000000 --- a/elements/product-glance/custom-elements.json +++ /dev/null @@ -1,103 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "product-glance.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "ProductGlance", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "description": "haxProperties integration via file reference", - "readonly": true - }, - { - "kind": "field", - "name": "title", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "title" - }, - { - "kind": "field", - "name": "subtitle", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "subtitle" - }, - { - "kind": "field", - "name": "icon", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "icon" - } - ], - "attributes": [ - { - "name": "title", - "type": { - "text": "string" - }, - "fieldName": "title" - }, - { - "name": "subtitle", - "type": { - "text": "string" - }, - "fieldName": "subtitle" - }, - { - "name": "icon", - "type": { - "text": "string" - }, - "fieldName": "icon" - } - ], - "superclass": { - "name": "SimpleColors", - "package": "@haxtheweb/simple-colors/simple-colors.js" - } - } - ], - "exports": [ - { - "kind": "js", - "name": "ProductGlance", - "declaration": { - "name": "ProductGlance", - "module": "product-glance.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "ProductGlance", - "module": "product-glance.js" - } - } - ] - } - ] -} diff --git a/elements/product-glance/demo/index.html b/elements/product-glance/demo/index.html index 1e1a9401b7..54e542066e 100644 --- a/elements/product-glance/demo/index.html +++ b/elements/product-glance/demo/index.html @@ -4,18 +4,17 @@ ProductGlance: product-glance Demo - - +
    diff --git a/elements/product-glance/index.html b/elements/product-glance/index.html index 054022567e..ccdc99158a 100644 --- a/elements/product-glance/index.html +++ b/elements/product-glance/index.html @@ -4,10 +4,10 @@ product-glance documentation - - + + - + diff --git a/elements/product-glance/package.json b/elements/product-glance/package.json index 079fc04a2f..a1edfac366 100644 --- a/elements/product-glance/package.json +++ b/elements/product-glance/package.json @@ -46,16 +46,13 @@ "dependencies": { "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/simple-icon": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/product-glance/test/product-glance-basic.test.js b/elements/product-glance/test/product-glance-basic.test.js index 6c46891e72..e3f4656980 100644 --- a/elements/product-glance/test/product-glance-basic.test.js +++ b/elements/product-glance/test/product-glance-basic.test.js @@ -5,118 +5,118 @@ import { ProductGlance } from "../product-glance.js"; describe("product-glance basic test", () => { let element; - + beforeEach(async () => { element = await fixture(html` `); }); - describe('Core Component', () => { - it('should be defined as a custom element', () => { - expect(customElements.get('product-glance')).to.exist; - expect(element.tagName.toLowerCase()).to.equal('product-glance'); + describe("Core Component", () => { + it("should be defined as a custom element", () => { + expect(customElements.get("product-glance")).to.exist; + expect(element.tagName.toLowerCase()).to.equal("product-glance"); }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; expect(element instanceof ProductGlance).to.be.true; }); - it('should have shadow DOM', () => { + it("should have shadow DOM", () => { expect(element.shadowRoot).to.exist; }); }); - describe('Properties', () => { - it('should accept title property from attribute', () => { - expect(element.title).to.equal('test-title'); + describe("Properties", () => { + it("should accept title property from attribute", () => { + expect(element.title).to.equal("test-title"); }); - it('should update title property', async () => { - element.title = 'New Title'; + it("should update title property", async () => { + element.title = "New Title"; await element.updateComplete; - expect(element.title).to.equal('New Title'); + expect(element.title).to.equal("New Title"); }); - it('should update subtitle property', async () => { - element.subtitle = 'Test Subtitle'; + it("should update subtitle property", async () => { + element.subtitle = "Test Subtitle"; await element.updateComplete; - expect(element.subtitle).to.equal('Test Subtitle'); + expect(element.subtitle).to.equal("Test Subtitle"); }); }); - describe('Template', () => { - it('should render wrapper div', async () => { + describe("Template", () => { + it("should render wrapper div", async () => { await element.updateComplete; - const wrapper = element.shadowRoot.querySelector('.wrapper'); + const wrapper = element.shadowRoot.querySelector(".wrapper"); expect(wrapper).to.exist; }); - it('should display title when provided', async () => { - element.title = 'Product Title'; + it("should display title when provided", async () => { + element.title = "Product Title"; await element.updateComplete; - - const titleDiv = element.shadowRoot.querySelector('.title-text'); - expect(titleDiv.textContent.trim()).to.include('Product Title'); + + const titleDiv = element.shadowRoot.querySelector(".title-text"); + expect(titleDiv.textContent.trim()).to.include("Product Title"); }); - it('should display subtitle when provided', async () => { - element.subtitle = 'Product Subtitle'; + it("should display subtitle when provided", async () => { + element.subtitle = "Product Subtitle"; await element.updateComplete; - - const subtitleDiv = element.shadowRoot.querySelector('.subtitle-text'); - expect(subtitleDiv.textContent.trim()).to.include('Product Subtitle'); + + const subtitleDiv = element.shadowRoot.querySelector(".subtitle-text"); + expect(subtitleDiv.textContent.trim()).to.include("Product Subtitle"); }); }); - describe('Slot Support', () => { - it('should support title slot', async () => { + describe("Slot Support", () => { + it("should support title slot", async () => { const slottedElement = await fixture(html` Slotted Title `); - + await slottedElement.updateComplete; - const titleSlot = slottedElement.shadowRoot.querySelector('slot[name="title"]'); + const titleSlot = + slottedElement.shadowRoot.querySelector('slot[name="title"]'); expect(titleSlot).to.exist; - + // Check that slotted content is assigned const assignedElements = titleSlot.assignedElements(); expect(assignedElements).to.have.length(1); - expect(assignedElements[0].textContent).to.equal('Slotted Title'); + expect(assignedElements[0].textContent).to.equal("Slotted Title"); }); - - it('should show property value when no slot content provided', async () => { + + it("should show property value when no slot content provided", async () => { const noSlotElement = await fixture(html` - - + `); - + await noSlotElement.updateComplete; - const titleDiv = noSlotElement.shadowRoot.querySelector('.title-text'); - - expect(titleDiv.textContent.trim()).to.include('Property Only'); + const titleDiv = noSlotElement.shadowRoot.querySelector(".title-text"); + + expect(titleDiv.textContent.trim()).to.include("Property Only"); }); }); - describe('SimpleColors', () => { - it('should have SimpleColors properties available', () => { - expect(element).to.have.property('accentColor'); - expect(element).to.have.property('dark'); - expect(element).to.have.property('contrast'); + describe("SimpleColors", () => { + it("should have SimpleColors properties available", () => { + expect(element).to.have.property("accentColor"); + expect(element).to.have.property("dark"); + expect(element).to.have.property("contrast"); }); - it('should handle accent color changes', async () => { - element.accentColor = 'blue'; + it("should handle accent color changes", async () => { + element.accentColor = "blue"; await element.updateComplete; - expect(element.accentColor).to.equal('blue'); + expect(element.accentColor).to.equal("blue"); }); - it('should have SimpleColors in prototype chain', () => { - expect(element.constructor.properties).to.include.keys('accentColor'); - expect(element.constructor.properties).to.include.keys('dark'); + it("should have SimpleColors in prototype chain", () => { + expect(element.constructor.properties).to.include.keys("accentColor"); + expect(element.constructor.properties).to.include.keys("dark"); }); }); diff --git a/elements/product-glance/test/product-glance-minimal.test.js b/elements/product-glance/test/product-glance-minimal.test.js index c9f3ce6957..f49a461c67 100644 --- a/elements/product-glance/test/product-glance-minimal.test.js +++ b/elements/product-glance/test/product-glance-minimal.test.js @@ -4,23 +4,25 @@ import "../product-glance.js"; import { ProductGlance } from "../product-glance.js"; describe("product-glance minimal test", () => { - it('should be defined as a custom element', () => { - expect(customElements.get('product-glance')).to.exist; + it("should be defined as a custom element", () => { + expect(customElements.get("product-glance")).to.exist; }); - it('should create an instance', async () => { + it("should create an instance", async () => { const element = await fixture(html``); expect(element).to.exist; expect(element instanceof ProductGlance).to.be.true; }); - it('should have shadow DOM', async () => { + it("should have shadow DOM", async () => { const element = await fixture(html``); expect(element.shadowRoot).to.exist; }); - it('should set title property', async () => { - const element = await fixture(html``); - expect(element.title).to.equal('test'); + it("should set title property", async () => { + const element = await fixture( + html``, + ); + expect(element.title).to.equal("test"); }); }); diff --git a/elements/product-glance/test/product-glance.test.js b/elements/product-glance/test/product-glance.test.js index 3634547ac5..832c502cf0 100644 --- a/elements/product-glance/test/product-glance.test.js +++ b/elements/product-glance/test/product-glance.test.js @@ -11,395 +11,400 @@ describe("product-glance test", () => { `); }); - describe('Component Structure', () => { - it('should be defined as a custom element', () => { - expect(customElements.get('product-glance')).to.exist; - expect(element.tagName.toLowerCase()).to.equal('product-glance'); + describe("Component Structure", () => { + it("should be defined as a custom element", () => { + expect(customElements.get("product-glance")).to.exist; + expect(element.tagName.toLowerCase()).to.equal("product-glance"); }); - it('should create an instance', () => { + it("should create an instance", () => { expect(element).to.exist; expect(element instanceof ProductGlance).to.be.true; }); - it('should have correct tag property', () => { - expect(ProductGlance.tag).to.equal('product-glance'); + it("should have correct tag property", () => { + expect(ProductGlance.tag).to.equal("product-glance"); }); - it('should extend SimpleColors', () => { - expect(element.constructor.name).to.equal('ProductGlance'); + it("should extend SimpleColors", () => { + expect(element.constructor.name).to.equal("ProductGlance"); }); - it('should have shadow DOM', () => { + it("should have shadow DOM", () => { expect(element.shadowRoot).to.exist; }); - it('should have HAX properties', () => { - expect(ProductGlance.haxProperties).to.be.a('string'); - expect(ProductGlance.haxProperties).to.include('product-glance.haxProperties.json'); + it("should have HAX properties", () => { + expect(ProductGlance.haxProperties).to.be.a("string"); + expect(ProductGlance.haxProperties).to.include( + "product-glance.haxProperties.json", + ); }); }); - describe('Default Properties', () => { - it('should accept title property from attribute', () => { + describe("Default Properties", () => { + it("should accept title property from attribute", () => { // Element is created with title="test-title" in beforeEach - expect(element.title).to.equal('test-title'); + expect(element.title).to.equal("test-title"); }); - it('should have default subtitle property when not specified', () => { + it("should have default subtitle property when not specified", () => { expect(element.subtitle).to.be.undefined; }); - it('should have default icon property when not specified', () => { + it("should have default icon property when not specified", () => { expect(element.icon).to.be.undefined; }); }); - describe('Property Updates', () => { - it('should update title property', async () => { - element.title = 'Test Title'; + describe("Property Updates", () => { + it("should update title property", async () => { + element.title = "Test Title"; await element.updateComplete; - expect(element.title).to.equal('Test Title'); + expect(element.title).to.equal("Test Title"); }); - it('should update subtitle property', async () => { - element.subtitle = 'Test Subtitle'; + it("should update subtitle property", async () => { + element.subtitle = "Test Subtitle"; await element.updateComplete; - expect(element.subtitle).to.equal('Test Subtitle'); + expect(element.subtitle).to.equal("Test Subtitle"); }); - it('should update icon property', async () => { - element.icon = 'star'; + it("should update icon property", async () => { + element.icon = "star"; await element.updateComplete; - expect(element.icon).to.equal('star'); + expect(element.icon).to.equal("star"); }); }); - describe('Template Rendering', () => { - it('should render wrapper div with grid layout', async () => { + describe("Template Rendering", () => { + it("should render wrapper div with grid layout", async () => { await element.updateComplete; - const wrapper = element.shadowRoot.querySelector('.wrapper'); + const wrapper = element.shadowRoot.querySelector(".wrapper"); expect(wrapper).to.exist; - + const computedStyle = getComputedStyle(wrapper); - expect(computedStyle.display).to.equal('grid'); + expect(computedStyle.display).to.equal("grid"); }); - it('should render icon-wrapper div', async () => { + it("should render icon-wrapper div", async () => { await element.updateComplete; - const iconWrapper = element.shadowRoot.querySelector('.icon-wrapper'); + const iconWrapper = element.shadowRoot.querySelector(".icon-wrapper"); expect(iconWrapper).to.exist; }); - it('should render text-wrapper div', async () => { + it("should render text-wrapper div", async () => { await element.updateComplete; - const textWrapper = element.shadowRoot.querySelector('.text-wrapper'); + const textWrapper = element.shadowRoot.querySelector(".text-wrapper"); expect(textWrapper).to.exist; }); - it('should render title-text div', async () => { + it("should render title-text div", async () => { await element.updateComplete; - const titleText = element.shadowRoot.querySelector('.title-text'); + const titleText = element.shadowRoot.querySelector(".title-text"); expect(titleText).to.exist; }); - it('should render subtitle-text div', async () => { + it("should render subtitle-text div", async () => { await element.updateComplete; - const subtitleText = element.shadowRoot.querySelector('.subtitle-text'); + const subtitleText = element.shadowRoot.querySelector(".subtitle-text"); expect(subtitleText).to.exist; }); - it('should display title when provided', async () => { - element.title = 'Product Title'; + it("should display title when provided", async () => { + element.title = "Product Title"; await element.updateComplete; - - const titleDiv = element.shadowRoot.querySelector('.title-text'); - expect(titleDiv.textContent.trim()).to.include('Product Title'); + + const titleDiv = element.shadowRoot.querySelector(".title-text"); + expect(titleDiv.textContent.trim()).to.include("Product Title"); }); - it('should display subtitle when provided', async () => { - element.subtitle = 'Product Subtitle'; + it("should display subtitle when provided", async () => { + element.subtitle = "Product Subtitle"; await element.updateComplete; - - const subtitleDiv = element.shadowRoot.querySelector('.subtitle-text'); - expect(subtitleDiv.textContent.trim()).to.include('Product Subtitle'); + + const subtitleDiv = element.shadowRoot.querySelector(".subtitle-text"); + expect(subtitleDiv.textContent.trim()).to.include("Product Subtitle"); }); - it('should render icon when provided', async () => { - element.icon = 'star'; + it("should render icon when provided", async () => { + element.icon = "star"; await element.updateComplete; - + // Wait a tick for dynamic import - await new Promise(resolve => setTimeout(resolve, 100)); - - const iconElement = element.shadowRoot.querySelector('simple-icon'); + await new Promise((resolve) => setTimeout(resolve, 100)); + + const iconElement = element.shadowRoot.querySelector("simple-icon"); expect(iconElement).to.exist; - expect(iconElement.getAttribute('icon')).to.equal('star'); + expect(iconElement.getAttribute("icon")).to.equal("star"); }); - it('should not render icon when not provided', async () => { + it("should not render icon when not provided", async () => { await element.updateComplete; - const iconElement = element.shadowRoot.querySelector('simple-icon'); + const iconElement = element.shadowRoot.querySelector("simple-icon"); expect(iconElement).to.not.exist; }); }); - describe('Icon Functionality - Basic Tests', () => { - it('should have icon property', async () => { - element.icon = 'star'; + describe("Icon Functionality - Basic Tests", () => { + it("should have icon property", async () => { + element.icon = "star"; await element.updateComplete; - expect(element.icon).to.equal('star'); + expect(element.icon).to.equal("star"); }); - it('should clear icon property', async () => { - element.icon = 'star'; + it("should clear icon property", async () => { + element.icon = "star"; await element.updateComplete; - element.icon = ''; + element.icon = ""; await element.updateComplete; - expect(element.icon).to.equal(''); + expect(element.icon).to.equal(""); }); }); - describe('Slot Content', () => { - it('should support title slot', async () => { + describe("Slot Content", () => { + it("should support title slot", async () => { const slottedElement = await fixture(html` Slotted Title `); - + await slottedElement.updateComplete; - const titleSlot = slottedElement.shadowRoot.querySelector('slot[name="title"]'); + const titleSlot = + slottedElement.shadowRoot.querySelector('slot[name="title"]'); expect(titleSlot).to.exist; }); - it('should support subtitle slot', async () => { + it("should support subtitle slot", async () => { const slottedElement = await fixture(html` Slotted Subtitle `); - + await slottedElement.updateComplete; - const subtitleSlot = slottedElement.shadowRoot.querySelector('slot[name="subtitle"]'); + const subtitleSlot = slottedElement.shadowRoot.querySelector( + 'slot[name="subtitle"]', + ); expect(subtitleSlot).to.exist; }); - it('should show slotted content when provided, replacing property values', async () => { + it("should show slotted content when provided, replacing property values", async () => { const slottedElement = await fixture(html` Slotted Title `); - + await slottedElement.updateComplete; - + // Get the assigned nodes of the slot to see what's actually displayed - const titleSlot = slottedElement.shadowRoot.querySelector('slot[name="title"]'); + const titleSlot = + slottedElement.shadowRoot.querySelector('slot[name="title"]'); const assignedElements = titleSlot.assignedElements(); - + expect(assignedElements).to.have.length.greaterThan(0); - expect(assignedElements[0].textContent).to.equal('Slotted Title'); + expect(assignedElements[0].textContent).to.equal("Slotted Title"); }); - - it('should show property value when no slot content provided', async () => { + + it("should show property value when no slot content provided", async () => { const noSlotElement = await fixture(html` - - + `); - + await noSlotElement.updateComplete; - const titleDiv = noSlotElement.shadowRoot.querySelector('.title-text'); - - expect(titleDiv.textContent.trim()).to.include('Property Only'); + const titleDiv = noSlotElement.shadowRoot.querySelector(".title-text"); + + expect(titleDiv.textContent.trim()).to.include("Property Only"); }); }); - describe('SimpleColors Integration', () => { - it('should inherit SimpleColors properties', () => { + describe("SimpleColors Integration", () => { + it("should inherit SimpleColors properties", () => { // Based on debug output: accentColor='grey', dark=false, contrast=undefined // SimpleColors properties are initialized in constructor or when accessed expect(element.accentColor).to.not.be.undefined; expect(element.dark).to.not.be.undefined; // Contrast can be undefined initially in SimpleColors - expect(element).to.have.property('contrast'); + expect(element).to.have.property("contrast"); }); - it('should handle accent color changes', async () => { - element.accentColor = 'blue'; + it("should handle accent color changes", async () => { + element.accentColor = "blue"; await element.updateComplete; - expect(element.accentColor).to.equal('blue'); + expect(element.accentColor).to.equal("blue"); }); - it('should handle dark mode changes', async () => { + it("should handle dark mode changes", async () => { element.dark = true; await element.updateComplete; expect(element.dark).to.be.true; }); - - it('should have SimpleColors in prototype chain', () => { - expect(element.constructor.properties).to.include.keys('accentColor'); - expect(element.constructor.properties).to.include.keys('dark'); + + it("should have SimpleColors in prototype chain", () => { + expect(element.constructor.properties).to.include.keys("accentColor"); + expect(element.constructor.properties).to.include.keys("dark"); }); }); - describe('Complex Scenarios', () => { - it('should handle all properties set together', async () => { - element.title = 'Complete Product'; - element.subtitle = 'Full Description'; - element.icon = 'star'; - element.accentColor = 'purple'; + describe("Complex Scenarios", () => { + it("should handle all properties set together", async () => { + element.title = "Complete Product"; + element.subtitle = "Full Description"; + element.icon = "star"; + element.accentColor = "purple"; element.dark = false; - + await element.updateComplete; - await new Promise(resolve => setTimeout(resolve, 100)); // Wait for icon import - - const titleDiv = element.shadowRoot.querySelector('.title-text'); - const subtitleDiv = element.shadowRoot.querySelector('.subtitle-text'); - const iconElement = element.shadowRoot.querySelector('simple-icon'); - - expect(titleDiv.textContent.trim()).to.include('Complete Product'); - expect(subtitleDiv.textContent.trim()).to.include('Full Description'); + await new Promise((resolve) => setTimeout(resolve, 100)); // Wait for icon import + + const titleDiv = element.shadowRoot.querySelector(".title-text"); + const subtitleDiv = element.shadowRoot.querySelector(".subtitle-text"); + const iconElement = element.shadowRoot.querySelector("simple-icon"); + + expect(titleDiv.textContent.trim()).to.include("Complete Product"); + expect(subtitleDiv.textContent.trim()).to.include("Full Description"); expect(iconElement).to.exist; - expect(iconElement.getAttribute('icon')).to.equal('star'); - expect(iconElement.getAttribute('accent-color')).to.equal('purple'); + expect(iconElement.getAttribute("icon")).to.equal("star"); + expect(iconElement.getAttribute("accent-color")).to.equal("purple"); }); - it('should handle empty content gracefully', async () => { - element.title = ''; - element.subtitle = ''; - element.icon = ''; - + it("should handle empty content gracefully", async () => { + element.title = ""; + element.subtitle = ""; + element.icon = ""; + await element.updateComplete; - - const titleDiv = element.shadowRoot.querySelector('.title-text'); - const subtitleDiv = element.shadowRoot.querySelector('.subtitle-text'); - const iconElement = element.shadowRoot.querySelector('simple-icon'); - + + const titleDiv = element.shadowRoot.querySelector(".title-text"); + const subtitleDiv = element.shadowRoot.querySelector(".subtitle-text"); + const iconElement = element.shadowRoot.querySelector("simple-icon"); + expect(titleDiv).to.exist; expect(subtitleDiv).to.exist; expect(iconElement).to.not.exist; }); }); - describe('CSS Custom Properties', () => { - it('should use CSS custom properties for styling', () => { + describe("CSS Custom Properties", () => { + it("should use CSS custom properties for styling", () => { const styles = element.constructor.styles; expect(styles).to.exist; - + // Check if styles contain custom properties const styleSheet = styles[1].cssText; - expect(styleSheet).to.include('--product-glance-icon-width'); - expect(styleSheet).to.include('--product-glance-internal-margin'); - expect(styleSheet).to.include('--product-glance-text-color'); + expect(styleSheet).to.include("--product-glance-icon-width"); + expect(styleSheet).to.include("--product-glance-internal-margin"); + expect(styleSheet).to.include("--product-glance-text-color"); }); - it('should have proper default display style', async () => { + it("should have proper default display style", async () => { await element.updateComplete; const computedStyle = getComputedStyle(element); - expect(computedStyle.display).to.equal('inline-block'); + expect(computedStyle.display).to.equal("inline-block"); }); }); - describe('Updated Lifecycle', () => { - it('should import simple-icon when icon property changes', async () => { + describe("Updated Lifecycle", () => { + it("should import simple-icon when icon property changes", async () => { // Initially no icon - expect(element.shadowRoot.querySelector('simple-icon')).to.not.exist; - + expect(element.shadowRoot.querySelector("simple-icon")).to.not.exist; + // Set icon - element.icon = 'star'; + element.icon = "star"; await element.updateComplete; - await new Promise(resolve => setTimeout(resolve, 100)); - + await new Promise((resolve) => setTimeout(resolve, 100)); + // Icon should be imported and rendered - expect(element.shadowRoot.querySelector('simple-icon')).to.exist; + expect(element.shadowRoot.querySelector("simple-icon")).to.exist; }); - it('should not import simple-icon when icon is cleared', async () => { + it("should not import simple-icon when icon is cleared", async () => { // Set icon first - element.icon = 'star'; + element.icon = "star"; await element.updateComplete; - await new Promise(resolve => setTimeout(resolve, 100)); - + await new Promise((resolve) => setTimeout(resolve, 100)); + // Clear icon - element.icon = ''; + element.icon = ""; await element.updateComplete; - + // Icon should not be rendered - expect(element.shadowRoot.querySelector('simple-icon')).to.not.exist; + expect(element.shadowRoot.querySelector("simple-icon")).to.not.exist; }); }); - describe('Edge Cases', () => { - it('should handle null values gracefully', async () => { + describe("Edge Cases", () => { + it("should handle null values gracefully", async () => { element.title = null; element.subtitle = null; element.icon = null; - + await element.updateComplete; - + expect(() => element.render()).to.not.throw; }); - it('should handle undefined values gracefully', async () => { + it("should handle undefined values gracefully", async () => { element.title = undefined; element.subtitle = undefined; element.icon = undefined; - + await element.updateComplete; - + expect(() => element.render()).to.not.throw; }); - it('should handle numeric values in text properties', async () => { + it("should handle numeric values in text properties", async () => { element.title = 123; element.subtitle = 456; - + await element.updateComplete; - - const titleDiv = element.shadowRoot.querySelector('.title-text'); - const subtitleDiv = element.shadowRoot.querySelector('.subtitle-text'); - - expect(titleDiv.textContent.trim()).to.include('123'); - expect(subtitleDiv.textContent.trim()).to.include('456'); + + const titleDiv = element.shadowRoot.querySelector(".title-text"); + const subtitleDiv = element.shadowRoot.querySelector(".subtitle-text"); + + expect(titleDiv.textContent.trim()).to.include("123"); + expect(subtitleDiv.textContent.trim()).to.include("456"); }); }); - describe('Multiple Instances', () => { - it('should support multiple independent instances', async () => { + describe("Multiple Instances", () => { + it("should support multiple independent instances", async () => { const element1 = await fixture(html` `); const element2 = await fixture(html` `); - + await element1.updateComplete; await element2.updateComplete; - await new Promise(resolve => setTimeout(resolve, 100)); - - expect(element1.title).to.equal('Product 1'); - expect(element2.title).to.equal('Product 2'); - - const icon1 = element1.shadowRoot.querySelector('simple-icon'); - const icon2 = element2.shadowRoot.querySelector('simple-icon'); - - expect(icon1?.getAttribute('icon')).to.equal('star'); - expect(icon2?.getAttribute('icon')).to.equal('heart'); + await new Promise((resolve) => setTimeout(resolve, 100)); + + expect(element1.title).to.equal("Product 1"); + expect(element2.title).to.equal("Product 2"); + + const icon1 = element1.shadowRoot.querySelector("simple-icon"); + const icon2 = element2.shadowRoot.querySelector("simple-icon"); + + expect(icon1 && icon1.getAttribute("icon")).to.equal("star"); + expect(icon2 && icon2.getAttribute("icon")).to.equal("heart"); }); }); - describe('Performance', () => { - it('should handle rapid property changes efficiently', async () => { + describe("Performance", () => { + it("should handle rapid property changes efficiently", async () => { const startTime = performance.now(); - + for (let i = 0; i < 50; i++) { element.title = `Product ${i}`; element.subtitle = `Description ${i}`; - element.icon = i % 2 === 0 ? 'star' : 'heart'; + element.icon = i % 2 === 0 ? "star" : "heart"; await element.updateComplete; } - + const endTime = performance.now(); expect(endTime - startTime).to.be.lessThan(5000); // Should complete within 5 seconds }); diff --git a/elements/product-offering/.gitignore b/elements/product-offering/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/product-offering/.gitignore +++ b/elements/product-offering/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/product-offering/custom-elements.json b/elements/product-offering/custom-elements.json deleted file mode 100644 index 643a1aa8a2..0000000000 --- a/elements/product-offering/custom-elements.json +++ /dev/null @@ -1,179 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "product-offering.js", - "declarations": [ - { - "kind": "class", - "description": "`product-offering`\n`Simple card for displaying product info`", - "name": "ProductOffering", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Convention we use", - "readonly": true - }, - { - "kind": "field", - "name": "alt", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"\"", - "attribute": "alt" - }, - { - "kind": "field", - "name": "accentColor", - "type": { - "text": "string" - }, - "default": "\"blue\"" - }, - { - "kind": "field", - "name": "source", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "source" - }, - { - "kind": "field", - "name": "icon", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "icon" - }, - { - "kind": "field", - "name": "title", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "title" - }, - { - "kind": "field", - "name": "_titleOne", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "_titleOne" - }, - { - "kind": "field", - "name": "_titleTwo", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "_titleTwo" - }, - { - "kind": "field", - "name": "description", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "description" - } - ], - "attributes": [ - { - "name": "alt", - "type": { - "text": "string" - }, - "default": "\"\"", - "fieldName": "alt" - }, - { - "name": "source", - "type": { - "text": "string" - }, - "fieldName": "source" - }, - { - "name": "icon", - "type": { - "text": "string" - }, - "fieldName": "icon" - }, - { - "name": "title", - "type": { - "text": "string" - }, - "fieldName": "title" - }, - { - "name": "_titleOne", - "type": { - "text": "string" - }, - "fieldName": "_titleOne" - }, - { - "name": "_titleTwo", - "type": { - "text": "string" - }, - "fieldName": "_titleTwo" - }, - { - "name": "description", - "type": { - "text": "string" - }, - "fieldName": "description" - } - ], - "mixins": [ - { - "name": "IntersectionObserverMixin", - "package": "@haxtheweb/intersection-element/lib/IntersectionObserverMixin.js" - } - ], - "superclass": { - "name": "SimpleColors", - "package": "@haxtheweb/simple-colors/simple-colors.js" - }, - "tagName": "product-offering", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "ProductOffering", - "module": "product-offering.js" - } - }, - { - "kind": "js", - "name": "ProductOffering", - "declaration": { - "name": "ProductOffering", - "module": "product-offering.js" - } - } - ] - } - ] -} diff --git a/elements/product-offering/demo/index.html b/elements/product-offering/demo/index.html index 03df0778de..6351645536 100644 --- a/elements/product-offering/demo/index.html +++ b/elements/product-offering/demo/index.html @@ -4,12 +4,11 @@ ProductOffering: product-offering Demo - +
    diff --git a/elements/progress-donut/index.html b/elements/progress-donut/index.html index 2cf8794ff3..520c03fe38 100755 --- a/elements/progress-donut/index.html +++ b/elements/progress-donut/index.html @@ -4,10 +4,10 @@ progress-donut documentation - - + + - + diff --git a/elements/progress-donut/package.json b/elements/progress-donut/package.json old mode 100755 new mode 100644 index e1cfd7fabe..b6609f072f --- a/elements/progress-donut/package.json +++ b/elements/progress-donut/package.json @@ -47,16 +47,13 @@ "@haxtheweb/lrndesign-chart": "^11.0.5", "@haxtheweb/schema-behaviors": "^11.0.5", "@haxtheweb/simple-colors": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/promise-progress/.gitignore b/elements/promise-progress/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/promise-progress/.gitignore +++ b/elements/promise-progress/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/promise-progress/demo/basic.html b/elements/promise-progress/demo/basic.html index d43e440015..47e9e4f9cd 100644 --- a/elements/promise-progress/demo/basic.html +++ b/elements/promise-progress/demo/basic.html @@ -7,11 +7,10 @@ - diff --git a/elements/promise-progress/demo/index.html b/elements/promise-progress/demo/index.html index dfb53b3795..976759a86b 100644 --- a/elements/promise-progress/demo/index.html +++ b/elements/promise-progress/demo/index.html @@ -7,7 +7,6 @@ - - + + - + diff --git a/elements/promise-progress/package.json b/elements/promise-progress/package.json index 7ad06729bc..ef180c7621 100644 --- a/elements/promise-progress/package.json +++ b/elements/promise-progress/package.json @@ -46,16 +46,13 @@ "dependencies": { "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/wc-autoload": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/q-r/.gitignore b/elements/q-r/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/q-r/.gitignore +++ b/elements/q-r/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/q-r/custom-elements.json b/elements/q-r/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/q-r/demo/index.html b/elements/q-r/demo/index.html index 315449e2e7..b15a2ef083 100644 --- a/elements/q-r/demo/index.html +++ b/elements/q-r/demo/index.html @@ -7,10 +7,10 @@ - +
    diff --git a/elements/q-r/index.html b/elements/q-r/index.html index 37e4cee63e..a86bd0aa3c 100755 --- a/elements/q-r/index.html +++ b/elements/q-r/index.html @@ -4,10 +4,10 @@ q-r documentation - - + + - + diff --git a/elements/q-r/package.json b/elements/q-r/package.json old mode 100755 new mode 100644 index 03e3aae055..9cffe7e5c7 --- a/elements/q-r/package.json +++ b/elements/q-r/package.json @@ -42,16 +42,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/es-global-bridge": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/radio-behaviors/.gitignore b/elements/radio-behaviors/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/radio-behaviors/.gitignore +++ b/elements/radio-behaviors/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/radio-behaviors/custom-elements.json b/elements/radio-behaviors/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/radio-behaviors/demo/index.html b/elements/radio-behaviors/demo/index.html index 8453b13a1a..90068ecfb0 100644 --- a/elements/radio-behaviors/demo/index.html +++ b/elements/radio-behaviors/demo/index.html @@ -8,7 +8,7 @@ diff --git a/elements/radio-behaviors/index.html b/elements/radio-behaviors/index.html index c9d6dbd24b..312162bdb4 100755 --- a/elements/radio-behaviors/index.html +++ b/elements/radio-behaviors/index.html @@ -4,11 +4,11 @@ simple-colors documentation - - + + - + diff --git a/elements/radio-behaviors/package.json b/elements/radio-behaviors/package.json old mode 100755 new mode 100644 index 1ab12651f3..76641e6829 --- a/elements/radio-behaviors/package.json +++ b/elements/radio-behaviors/package.json @@ -40,10 +40,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/radio-behaviors/radio-behaviors.js b/elements/radio-behaviors/radio-behaviors.js index b0605a9d6c..57b8b71b08 100644 --- a/elements/radio-behaviors/radio-behaviors.js +++ b/elements/radio-behaviors/radio-behaviors.js @@ -121,7 +121,7 @@ const RadioBehaviors = function (SuperClass) { item = typeof item === "string" && item.trim().length > 0 ? this._getItemById(item) - : typeof item === "integer" + : typeof item === "number" ? this._getItemByIndex(item) : item; diff --git a/elements/relative-heading/.gitignore b/elements/relative-heading/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/relative-heading/.gitignore +++ b/elements/relative-heading/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/relative-heading/custom-elements.json b/elements/relative-heading/custom-elements.json deleted file mode 100755 index 06a2bc1a25..0000000000 --- a/elements/relative-heading/custom-elements.json +++ /dev/null @@ -1,966 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "relative-heading.js", - "declarations": [ - { - "kind": "class", - "description": "`relative-heading`\n`outputs the correct heading hierarchy based on parent heading`", - "name": "RelativeHeading", - "members": [ - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "field", - "name": "anchored", - "description": "gets whether heading is currently anchored", - "readonly": true, - "return": { - "type": { - "text": "boolean" - } - } - }, - { - "kind": "field", - "name": "button", - "readonly": true - }, - { - "kind": "method", - "name": "_handleCopyClick" - }, - { - "kind": "field", - "name": "linkAlignRight", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "label for copy link's button", - "default": "false", - "attribute": "link-align-right", - "reflects": true - }, - { - "kind": "field", - "name": "disableLink", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "The relative-heading resource's UUID.", - "default": "false", - "attribute": "disable-link" - }, - { - "kind": "field", - "name": "linkIcon", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "icon for copy link's button", - "default": "\"link\"", - "attribute": "linkIcon" - }, - { - "kind": "field", - "name": "linkLabel", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "label for copy link's button", - "default": "\"Get link\"", - "attribute": "linkLabel" - }, - { - "kind": "field", - "name": "closeIcon", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "overrides state manager's default icon for copy link's toast's close button", - "attribute": "closeIcon" - }, - { - "kind": "field", - "name": "closeLabel", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "overrides state manager's default label for copy link's toast's close button", - "attribute": "closeLabel" - }, - { - "kind": "field", - "name": "copyMessage", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "overrides state manager's default message for copy link's toast", - "attribute": "copyMessage" - }, - { - "kind": "field", - "name": "template", - "readonly": true, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "field", - "name": "observer", - "description": "returns mutation observer", - "readonly": true, - "return": { - "type": { - "text": "object" - } - }, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "field", - "name": "manager", - "description": "returns state manager", - "readonly": true, - "return": { - "type": { - "text": "object" - } - }, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "method", - "name": "checkId", - "description": "ensures that id is not blank", - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "method", - "name": "_setLevel", - "parameters": [ - { - "name": "level", - "description": "of heading", - "type": { - "text": "number" - } - } - ], - "description": "sets the heading level", - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "method", - "name": "updateContents", - "description": "unwraps tags on slotted content", - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "method", - "name": "_generateUUID", - "description": "generates a unique id", - "return": { - "type": { - "text": "string" - } - }, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "field", - "name": "parent", - "privacy": "public", - "type": { - "text": "null" - }, - "description": "The parent relative-heading's UUID.", - "default": "null", - "attribute": "parent", - "reflects": true, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "field", - "name": "__level", - "privacy": "public", - "type": { - "text": "number" - }, - "description": "The parent relative-heading's UUID.", - "attribute": "level", - "reflects": true, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "field", - "name": "defaultLevel", - "privacy": "public", - "type": { - "text": "number" - }, - "description": "The default heading level (1-6),\neg., 1 for

    , if there is no parent.", - "default": "1", - "attribute": "default-level", - "reflects": true, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "field", - "name": "id", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "The relative-heading's UUID.", - "attribute": "id", - "reflects": true, - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - } - ], - "attributes": [ - { - "name": "closeIcon", - "type": { - "text": "string" - }, - "description": "overrides state manager's default icon for copy link's toast's close button", - "fieldName": "closeIcon" - }, - { - "name": "closeLabel", - "type": { - "text": "string" - }, - "description": "overrides state manager's default label for copy link's toast's close button", - "fieldName": "closeLabel" - }, - { - "name": "copyMessage", - "type": { - "text": "string" - }, - "description": "overrides state manager's default message for copy link's toast", - "fieldName": "copyMessage" - }, - { - "name": "disable-link", - "type": { - "text": "boolean" - }, - "description": "The relative-heading resource's UUID.", - "default": "false", - "fieldName": "disableLink" - }, - { - "name": "link-align-right", - "type": { - "text": "boolean" - }, - "description": "label for copy link's button", - "default": "false", - "fieldName": "linkAlignRight" - }, - { - "name": "linkIcon", - "type": { - "text": "string" - }, - "description": "icon for copy link's button", - "default": "\"link\"", - "fieldName": "linkIcon" - }, - { - "name": "linkLabel", - "type": { - "text": "string" - }, - "description": "label for copy link's button", - "default": "\"Get link\"", - "fieldName": "linkLabel" - }, - { - "name": "default-level", - "type": { - "text": "number" - }, - "description": "The default heading level (1-6),\neg., 1 for

    , if there is no parent.", - "default": "1", - "fieldName": "defaultLevel", - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "name": "id", - "type": { - "text": "string" - }, - "description": "The relative-heading's UUID.", - "fieldName": "id", - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "name": "parent", - "type": { - "text": "null" - }, - "description": "The parent relative-heading's UUID.", - "default": "null", - "fieldName": "parent", - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "name": "level", - "type": { - "text": "number" - }, - "description": "The parent relative-heading's UUID.", - "fieldName": "__level", - "inheritedFrom": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - } - ], - "superclass": { - "name": "RelativeHeadingLite", - "module": "/lib/relative-heading-lite.js" - }, - "tagName": "relative-heading", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "RelativeHeading", - "module": "relative-heading.js" - } - }, - { - "kind": "js", - "name": "RelativeHeading", - "declaration": { - "name": "RelativeHeading", - "module": "relative-heading.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/relative-heading-lite.js", - "declarations": [ - { - "kind": "class", - "description": "`relative-heading-lite`\n`outputs the correct heading hierarchy based on parent heading`", - "name": "RelativeHeadingLite", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true - }, - { - "kind": "field", - "name": "template", - "readonly": true - }, - { - "kind": "field", - "name": "observer", - "description": "returns mutation observer", - "readonly": true, - "return": { - "type": { - "text": "object" - } - } - }, - { - "kind": "field", - "name": "manager", - "description": "returns state manager", - "readonly": true, - "return": { - "type": { - "text": "object" - } - } - }, - { - "kind": "method", - "name": "checkId", - "description": "ensures that id is not blank" - }, - { - "kind": "method", - "name": "_setLevel", - "parameters": [ - { - "name": "level", - "description": "of heading", - "type": { - "text": "number" - } - } - ], - "description": "sets the heading level" - }, - { - "kind": "method", - "name": "updateContents", - "description": "unwraps tags on slotted content" - }, - { - "kind": "method", - "name": "_generateUUID", - "description": "generates a unique id", - "return": { - "type": { - "text": "string" - } - } - }, - { - "kind": "field", - "name": "parent", - "privacy": "public", - "type": { - "text": "null" - }, - "description": "The parent relative-heading's UUID.", - "default": "null", - "attribute": "parent", - "reflects": true - }, - { - "kind": "field", - "name": "__level", - "privacy": "public", - "type": { - "text": "number" - }, - "description": "The parent relative-heading's UUID.", - "attribute": "level", - "reflects": true - }, - { - "kind": "field", - "name": "defaultLevel", - "privacy": "public", - "type": { - "text": "number" - }, - "description": "The default heading level (1-6),\neg., 1 for

    , if there is no parent.", - "default": "1", - "attribute": "default-level", - "reflects": true - }, - { - "kind": "field", - "name": "id", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "The relative-heading's UUID.", - "attribute": "id", - "reflects": true - } - ], - "attributes": [ - { - "name": "default-level", - "type": { - "text": "number" - }, - "description": "The default heading level (1-6),\neg., 1 for

    , if there is no parent.", - "default": "1", - "fieldName": "defaultLevel" - }, - { - "name": "id", - "type": { - "text": "string" - }, - "description": "The relative-heading's UUID.", - "fieldName": "id" - }, - { - "name": "parent", - "type": { - "text": "null" - }, - "description": "The parent relative-heading's UUID.", - "default": "null", - "fieldName": "parent" - }, - { - "name": "level", - "type": { - "text": "number" - }, - "description": "The parent relative-heading's UUID.", - "fieldName": "__level" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "relative-heading-lite", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - }, - { - "kind": "js", - "name": "RelativeHeadingLite", - "declaration": { - "name": "RelativeHeadingLite", - "module": "lib/relative-heading-lite.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/relative-heading-state-manager.js", - "declarations": [ - { - "kind": "class", - "description": "`relative-heading-state-manager`\n` A utility that determines headings relative to their parents.`", - "name": "RelativeHeadingStateManager", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Store the tag name to make it easier to obtain directly.", - "readonly": true - }, - { - "kind": "field", - "name": "copyUrl", - "description": "gets URL to be copied", - "readonly": true, - "return": { - "type": { - "text": "string" - } - } - }, - { - "kind": "method", - "name": "useCopyLink", - "description": "imports toast if needed and not already loaded" - }, - { - "kind": "method", - "name": "copyLink", - "parameters": [ - { - "name": "heading" - }, - { - "description": "heading", - "name": "active", - "type": { - "text": "object" - } - } - ], - "description": "handles copying the share link" - }, - { - "kind": "method", - "name": "closeCopyLink", - "description": "handles closing link toast" - }, - { - "kind": "method", - "name": "addHeading", - "parameters": [ - { - "name": "heading", - "description": "to be added", - "type": { - "text": "object" - } - } - ], - "description": "adds heading to manager data" - }, - { - "kind": "method", - "name": "removeHeading", - "parameters": [ - { - "name": "heading", - "description": "to be removed", - "type": { - "text": "object" - } - } - ], - "description": "adds heading from manager data" - }, - { - "kind": "method", - "name": "updateId", - "parameters": [ - { - "name": "heading", - "description": "to be updated", - "type": { - "text": "object" - } - }, - { - "name": "old", - "default": "null", - "description": "heading id", - "type": { - "text": "string" - } - } - ], - "description": "updates heading id in manager data" - }, - { - "kind": "method", - "name": "updateParent", - "parameters": [ - { - "name": "heading", - "description": "to be updated", - "type": { - "text": "object" - } - }, - { - "name": "old", - "default": "null", - "description": "heading parent", - "type": { - "text": "string" - } - } - ], - "description": "updates heading parent id in manager data" - }, - { - "kind": "method", - "name": "updateDefaultLevel", - "parameters": [ - { - "name": "heading", - "description": "to be updated", - "type": { - "text": "object" - } - }, - { - "name": "old", - "default": "null" - } - ], - "description": "updates heading level based on default level" - }, - { - "kind": "method", - "name": "addSubhead", - "parameters": [ - { - "name": "heading", - "description": "to be added", - "type": { - "text": "object" - } - } - ], - "description": "adds heading to subhead data" - }, - { - "kind": "method", - "name": "removeSubhead", - "parameters": [ - { - "name": "id", - "description": "to be updated", - "type": { - "text": "string" - } - }, - { - "name": "heading", - "description": "to be removed", - "type": { - "text": "object" - } - } - ], - "description": "removes heading from subhead data" - }, - { - "kind": "method", - "name": "setHeading", - "parameters": [ - { - "name": "id", - "description": "to be updated", - "type": { - "text": "string" - } - }, - { - "name": "heading", - "description": "to be set", - "type": { - "text": "object" - } - } - ], - "description": "sets manager's heading data" - }, - { - "kind": "method", - "name": "updateLevel", - "parameters": [ - { - "name": "heading", - "description": "to be updated", - "type": { - "text": "object" - } - } - ], - "description": "updates heading level" - }, - { - "kind": "field", - "name": "closeIcon", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "icon for copy link's toast's close button", - "default": "\"close\"", - "attribute": "closeIcon" - }, - { - "kind": "field", - "name": "closeLabel", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "label for copy link's toast's close button", - "default": "\"Close\"", - "attribute": "closeLabel" - }, - { - "kind": "field", - "name": "copyMessage", - "privacy": "public", - "type": { - "text": "string" - }, - "description": "message for copy link's toast", - "default": "\"Copied to Clipboard\"", - "attribute": "copyMessage" - }, - { - "kind": "field", - "name": "headings", - "privacy": "public", - "type": { - "text": "array" - }, - "description": "Stores an array of all the players on the page.", - "default": "[]", - "attribute": "headings" - }, - { - "kind": "field", - "name": "copyHeading", - "privacy": "public", - "type": { - "text": "object" - }, - "description": "active heading for copying link", - "default": "{}", - "attribute": "copyHeading" - }, - { - "kind": "field", - "name": "usesCopyLink", - "privacy": "public", - "type": { - "text": "boolean" - }, - "description": "indicates is toast is already imported for copy link feature", - "default": "false", - "attribute": "usesCopyLink" - } - ], - "attributes": [ - { - "name": "closeIcon", - "type": { - "text": "string" - }, - "description": "icon for copy link's toast's close button", - "default": "\"close\"", - "fieldName": "closeIcon" - }, - { - "name": "closeLabel", - "type": { - "text": "string" - }, - "description": "label for copy link's toast's close button", - "default": "\"Close\"", - "fieldName": "closeLabel" - }, - { - "name": "copyHeading", - "type": { - "text": "object" - }, - "description": "active heading for copying link", - "default": "{}", - "fieldName": "copyHeading" - }, - { - "name": "copyMessage", - "type": { - "text": "string" - }, - "description": "message for copy link's toast", - "default": "\"Copied to Clipboard\"", - "fieldName": "copyMessage" - }, - { - "name": "headings", - "type": { - "text": "array" - }, - "description": "Stores an array of all the players on the page.", - "default": "[]", - "fieldName": "headings" - }, - { - "name": "usesCopyLink", - "type": { - "text": "boolean" - }, - "description": "indicates is toast is already imported for copy link feature", - "default": "false", - "fieldName": "usesCopyLink" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "relative-heading-state-manager", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "RelativeHeadingStateManager", - "module": "lib/relative-heading-state-manager.js" - } - }, - { - "kind": "js", - "name": "RelativeHeadingStateManager", - "declaration": { - "name": "RelativeHeadingStateManager", - "module": "lib/relative-heading-state-manager.js" - } - } - ] - } - ] -} diff --git a/elements/relative-heading/demo/index.html b/elements/relative-heading/demo/index.html index e8305a46cf..0a7666f14e 100644 --- a/elements/relative-heading/demo/index.html +++ b/elements/relative-heading/demo/index.html @@ -9,7 +9,7 @@ diff --git a/elements/relative-heading/demo/lite.html b/elements/relative-heading/demo/lite.html index c4461f0a08..93ec8b3379 100644 --- a/elements/relative-heading/demo/lite.html +++ b/elements/relative-heading/demo/lite.html @@ -9,7 +9,7 @@ diff --git a/elements/relative-heading/demo/nolinks.html b/elements/relative-heading/demo/nolinks.html index 83c10cce21..e455e645b3 100644 --- a/elements/relative-heading/demo/nolinks.html +++ b/elements/relative-heading/demo/nolinks.html @@ -9,7 +9,7 @@ diff --git a/elements/relative-heading/demo/rightalign.html b/elements/relative-heading/demo/rightalign.html index 4f8b1a2479..cebb17ea67 100644 --- a/elements/relative-heading/demo/rightalign.html +++ b/elements/relative-heading/demo/rightalign.html @@ -9,7 +9,7 @@ diff --git a/elements/relative-heading/index.html b/elements/relative-heading/index.html index ac1c812775..1e7b7f5ef0 100755 --- a/elements/relative-heading/index.html +++ b/elements/relative-heading/index.html @@ -4,10 +4,10 @@ relative-heading documentation - - + + - + diff --git a/elements/relative-heading/package.json b/elements/relative-heading/package.json old mode 100755 new mode 100644 index a42206f590..a16e99dbb1 --- a/elements/relative-heading/package.json +++ b/elements/relative-heading/package.json @@ -45,17 +45,13 @@ "@haxtheweb/hax-iconset": "^11.0.0", "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/simple-toast": "^11.0.5", - "@polymer/paper-toast": "3.0.1", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/relative-heading/relative-heading.js b/elements/relative-heading/relative-heading.js index 0300348d7f..58a84951b5 100644 --- a/elements/relative-heading/relative-heading.js +++ b/elements/relative-heading/relative-heading.js @@ -101,7 +101,17 @@ class RelativeHeading extends RelativeHeadingLite { "outputs the correct heading hierarchy based on parent's heading", icon: "icons:android", color: "green", - tags: ["Writing", "heading", "header", "h1", "h2", "h3", "h4", "h5", "h6"], + tags: [ + "Writing", + "heading", + "header", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + ], handles: [], meta: { author: "HAXTheWeb core team", diff --git a/elements/replace-tag/.gitignore b/elements/replace-tag/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/replace-tag/.gitignore +++ b/elements/replace-tag/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/replace-tag/index.html b/elements/replace-tag/index.html index 59f31992cb..cc965fe368 100644 --- a/elements/replace-tag/index.html +++ b/elements/replace-tag/index.html @@ -4,8 +4,8 @@ replace-tag documentation - - + +
      diff --git a/elements/replace-tag/package.json b/elements/replace-tag/package.json index 7934ccf420..8259cffcfb 100644 --- a/elements/replace-tag/package.json +++ b/elements/replace-tag/package.json @@ -50,10 +50,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/responsive-grid/.gitignore b/elements/responsive-grid/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/responsive-grid/.gitignore +++ b/elements/responsive-grid/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/responsive-grid/custom-elements.json b/elements/responsive-grid/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/responsive-grid/demo/index.html b/elements/responsive-grid/demo/index.html index 61a46c9a7f..f3fb4bf06a 100644 --- a/elements/responsive-grid/demo/index.html +++ b/elements/responsive-grid/demo/index.html @@ -7,10 +7,10 @@ - + Performance Test diff --git a/elements/responsive-grid/demo/performancetest.html b/elements/responsive-grid/demo/performancetest.html index 2a0c95b2d8..2fdbb532c3 100644 --- a/elements/responsive-grid/demo/performancetest.html +++ b/elements/responsive-grid/demo/performancetest.html @@ -11,7 +11,7 @@ - + diff --git a/elements/simple-colors/lib/simple-colors-polymer.js b/elements/simple-colors/lib/simple-colors-polymer.js deleted file mode 100644 index 61dbd06100..0000000000 --- a/elements/simple-colors/lib/simple-colors-polymer.js +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright 2018 The Pennsylvania State University - * @license Apache-2.0, see License.md for full text. - */ -import { html, PolymerElement } from "@polymer/polymer/polymer-element.js"; -import "@haxtheweb/simple-colors-shared-styles/simple-colors-shared-styles.js"; -import "./simple-colors-shared-styles-polymer.js"; - -/** - * `simple-colors-polymer` - * shared set of styles for Polymer @haxtheweb - * - * @polymer - * @element simple-colors-polymer - */ - -class SimpleColorsPolymer extends PolymerElement { - // render function - static get template() { - return html` - - - `; - } - - // properties available to the custom element for data binding - static get properties() { - return { - /** - * a selected accent-color: grey, red, pink, purple, etc. - */ - accentColor: { - name: "accentColor", - type: String, - value: "grey", - reflectToAttribute: true, - notify: true, - }, - /** - * make the default theme dark? - */ - dark: { - name: "dark", - type: Boolean, - value: false, - reflectToAttribute: true, - notify: true, - }, - /** - * make the default theme dark? - */ - colors: { - name: "colors", - type: Object, - value: globalThis.SimpleColorsSharedStyles.colors, - notify: true, - }, - }; - } - - static get tag() { - return "simple-colors-polymer"; - } - - constructor() { - super(); - this.__utils = globalThis.SimpleColorsSharedStyles.requestAvailability(); - this.colors = globalThis.SimpleColorsSharedStyles.colors; - } - - /** - * gets the current shade - * - * @param {string} the shade - * @param {number} the inverted shade - */ - invertShade(shade) { - return this.__utils.invertShade(shade); - } - - /** - * gets the color information of a given CSS variable or class - * - * @param {string} the CSS variable (eg. `--simple-colors-fixed-theme-red-3`) or a class (eg. `.simple-colors-fixed-theme-red-3-text`) - * @param {object} an object that includes the theme, color, and shade information - */ - getColorInfo(colorName) { - return this.__utils.getColorInfo(colorName); - } - - /** - * returns a variable based on color name, shade, and fixed theme - * - * @param {string} the color name - * @param {number} the color shade - * @param {boolean} the color shade - * @returns {string} the CSS Variable - */ - makeVariable(color = "grey", shade = 1, theme = "default") { - return this.__utils.makeVariable( - (color = "grey"), - (shade = 1), - (theme = "default"), - ); - } - - /** - * for large or small text given a color and its shade, - * lists all the colors and shades that would be - * WCAG 2.0 AA-compliant for contrast - * - * @param {boolean} large text? >= 18pt || (bold && >= 14pt) - * @param {string} color name, e.g. "deep-purple" - * @param {string} color shade, e.g. 3 - * @param {object} all of the WCAG 2.0 AA-compliant colors and shades - */ - getContrastingColors(colorName, colorShade, isLarge) { - return this.__utils.getContrastingColors(colorName, colorShade, isLarge); - } - - /** - * for large or small text given a color and its shade, - * lists all the shades of another color that would be - * WCAG 2.0 AA-compliant for contrast - * - * @param {boolean} large text? >= 18pt || (bold && >= 14pt) - * @param {string} color name, e.g. "deep-purple" - * @param {string} color shade, e.g. 3 - * @param {string} contrasting color name, e.g. "grey" - * @param {array} all of the WCAG 2.0 AA-compliant shades of the contrasting color - */ - getContrastingShades(isLarge, colorName, colorShade, contrastName) { - return this.__utils.getContrastingShades( - isLarge, - colorName, - colorShade, - contrastName, - ); - } - - /** - * determines if two shades are WCAG 2.0 AA-compliant for contrast - * - * @param {boolean} large text? >= 18pt || (bold && >= 14pt) - * @param {string} color name, e.g. "deep-purple" - * @param {string} color shade, e.g. 3 - * @param {string} contrasting color name, e.g. "grey" - * @param {string} contrast shade, e.g. 12 - * @param {boolean} whether or not the contrasting shade is WCAG 2.0 AA-compliant - */ - isContrastCompliant( - isLarge, - colorName, - colorShade, - contrastName, - contrastShade, - ) { - return this.__utils.isContrastCompliant( - isLarge, - colorName, - colorShade, - contrastName, - contrastShade, - ); - } -} -globalThis.customElements.define(SimpleColorsPolymer.tag, SimpleColorsPolymer); -export { SimpleColorsPolymer }; diff --git a/elements/simple-colors/lib/simple-colors-shared-styles-polymer.js b/elements/simple-colors/lib/simple-colors-shared-styles-polymer.js deleted file mode 100644 index 342422faf5..0000000000 --- a/elements/simple-colors/lib/simple-colors-shared-styles-polymer.js +++ /dev/null @@ -1,1162 +0,0 @@ -/** - * Copyright 2018 The Pennsylvania State University - * @license Apache-2.0, see License.md for full text. - */ -import { html } from "@polymer/polymer/polymer-element.js"; -/** - * `simple-colors-shared-styles-polymer` - * a shared set of styles for Polymer @haxtheweb - * - * @pseudoElement - * @polymer - * @element simple-colors-shared-styles-polymer - */ - -const StyleElement = globalThis.document.createElement("dom-module"); - -// styles -const css = html` - -`; - -StyleElement.appendChild(css); -StyleElement.register("simple-colors-shared-styles-polymer"); - -export { css }; diff --git a/elements/simple-colors/package.json b/elements/simple-colors/package.json old mode 100755 new mode 100644 index cd6d2c99ef..f8b06bea57 --- a/elements/simple-colors/package.json +++ b/elements/simple-colors/package.json @@ -42,21 +42,14 @@ }, "license": "Apache-2.0", "dependencies": { - "@haxtheweb/a11y-utils": "^11.0.0", "@haxtheweb/simple-colors-shared-styles": "^11.0.0", - "@haxtheweb/simple-modal": "^11.0.5", - "@haxtheweb/simple-picker": "^11.0.0", - "@polymer/polymer": "3.5.2", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-cta/.gitignore b/elements/simple-cta/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-cta/.gitignore +++ b/elements/simple-cta/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-cta/demo/index.html b/elements/simple-cta/demo/index.html index bd7d1f721d..8d6417a1d1 100644 --- a/elements/simple-cta/demo/index.html +++ b/elements/simple-cta/demo/index.html @@ -4,16 +4,15 @@ SimpleCta: simple-cta Demo - - +
      diff --git a/elements/simple-cta/index.html b/elements/simple-cta/index.html index 51c737f07b..0a0bc6d731 100644 --- a/elements/simple-cta/index.html +++ b/elements/simple-cta/index.html @@ -4,10 +4,10 @@ simple-cta documentation - - + + - + diff --git a/elements/simple-cta/lib/lrnsys-button.js b/elements/simple-cta/lib/lrnsys-button.js new file mode 100644 index 0000000000..c06ac44640 --- /dev/null +++ b/elements/simple-cta/lib/lrnsys-button.js @@ -0,0 +1,39 @@ +/** + * Legacy wrapper for lrnsys-button + * + * This makes behave like , while preserving + * the older lrnsys-button API (notably `href` + `label`). + */ +import { SimpleCta } from '@haxtheweb/simple-cta/simple-cta.js' + +class LrnsysButton extends SimpleCta { + static get tag () { + return 'lrnsys-button' + } + + static get properties () { + return { + ...super.properties, + // legacy attribute used in older content; we map this to `link` + href: { type: String }, + } + } + + updated (changedProperties) { + if (super.updated) { + super.updated(changedProperties) + } + changedProperties.forEach((oldValue, propName) => { + // if legacy `href` is set and `link` is not, map it through + if (propName === 'href' && this.href && !this.link) { + this.link = this.href + } + }) + } +} + +if (!globalThis.customElements.get(LrnsysButton.tag)) { + globalThis.customElements.define(LrnsysButton.tag, LrnsysButton) +} + +export { LrnsysButton } diff --git a/elements/simple-cta/package.json b/elements/simple-cta/package.json index 80682a23c2..e3642bfb0a 100644 --- a/elements/simple-cta/package.json +++ b/elements/simple-cta/package.json @@ -48,16 +48,13 @@ "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/utils": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-cta/simple-cta.js b/elements/simple-cta/simple-cta.js index ea51a35d87..9826a8e23b 100644 --- a/elements/simple-cta/simple-cta.js +++ b/elements/simple-cta/simple-cta.js @@ -205,7 +205,10 @@ class SimpleCta extends DDDPulseEffectSuper( return { type: "element", canScale: true, - + designSystem: { + primary: true, + accent: true, + }, canEditSource: true, gizmo: { title: "Call to action", @@ -242,13 +245,6 @@ class SimpleCta extends DDDPulseEffectSuper( noCamera: true, required: true, }, - { - property: "accentColor", - title: "Accent Color", - description: "An optional accent color.", - inputMethod: "colorpicker", - icon: "editor:format-color-fill", - }, { property: "hideIcon", title: "Hide icon", diff --git a/elements/simple-datetime/.gitignore b/elements/simple-datetime/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-datetime/.gitignore +++ b/elements/simple-datetime/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-datetime/custom-elements.json b/elements/simple-datetime/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/simple-datetime/demo/index.html b/elements/simple-datetime/demo/index.html index d4d738c6ea..9cc99d0f04 100644 --- a/elements/simple-datetime/demo/index.html +++ b/elements/simple-datetime/demo/index.html @@ -7,10 +7,10 @@ - +
      diff --git a/elements/simple-datetime/index.html b/elements/simple-datetime/index.html index 6e443aeb1e..2cccedeec8 100755 --- a/elements/simple-datetime/index.html +++ b/elements/simple-datetime/index.html @@ -4,10 +4,10 @@ simple-datetime documentation - - + + - + diff --git a/elements/simple-datetime/package.json b/elements/simple-datetime/package.json old mode 100755 new mode 100644 index 95a3ddc188..10209cb4d6 --- a/elements/simple-datetime/package.json +++ b/elements/simple-datetime/package.json @@ -47,10 +47,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-emoji/.gitignore b/elements/simple-emoji/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-emoji/.gitignore +++ b/elements/simple-emoji/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-emoji/custom-elements.json b/elements/simple-emoji/custom-elements.json deleted file mode 100644 index 12946e91f9..0000000000 --- a/elements/simple-emoji/custom-elements.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "simple-emoji.js", - "declarations": [ - { - "kind": "class", - "description": "`simple-emoji`\n`simplify emoji creation and management`", - "name": "SimpleEmoji", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Convention we use", - "readonly": true - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "simple-emoji", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "SimpleEmoji", - "module": "simple-emoji.js" - } - }, - { - "kind": "js", - "name": "SimpleEmoji", - "declaration": { - "name": "SimpleEmoji", - "module": "simple-emoji.js" - } - } - ] - } - ] -} diff --git a/elements/simple-emoji/demo/index.html b/elements/simple-emoji/demo/index.html index 4f2b51e75b..45c8a16430 100644 --- a/elements/simple-emoji/demo/index.html +++ b/elements/simple-emoji/demo/index.html @@ -4,15 +4,14 @@ SimpleEmoji: simple-emoji Demo - - +
      diff --git a/elements/simple-emoji/index.html b/elements/simple-emoji/index.html index 5971e20d08..8b1c2f3cd8 100644 --- a/elements/simple-emoji/index.html +++ b/elements/simple-emoji/index.html @@ -4,10 +4,10 @@ simple-emoji documentation - - + + - + diff --git a/elements/simple-emoji/package.json b/elements/simple-emoji/package.json index caf31ec8f1..425b771213 100644 --- a/elements/simple-emoji/package.json +++ b/elements/simple-emoji/package.json @@ -44,16 +44,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-fields/.gitignore b/elements/simple-fields/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-fields/.gitignore +++ b/elements/simple-fields/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-fields/custom-elements.json b/elements/simple-fields/custom-elements.json index 5288faf6c2..af3cc4f8e7 100644 --- a/elements/simple-fields/custom-elements.json +++ b/elements/simple-fields/custom-elements.json @@ -8,7 +8,7 @@ "declarations": [ { "kind": "class", - "description": "`simple-fields`\nUses JSON Schema to display a series of fields\n\n### Styling\n`` provides following custom properties\nfor styling:\n\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-margin` | vertical margin around container | 16px\n`--simple-fields-margin-small` | smaller vertical margin above field itself | 8px\n`--simple-fields-border-radus` | default border-radius | 2px\n`--simple-fields-color` | text color | black\n`--simple-fields-error-color` | error text color | #b40000\n`--simple-fields-accent-color` | accent text/underline color | #3f51b5\n`--simple-fields-border-color` | border-/underline color | #999\n`--simple-fields-border-color-light` | used for range tracks | #ccc\n`--simple-fields-faded-error-color` | used for range tracks | #ffc0c0\n\n#### Field text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-font-size` | font-size of field | 16px\n`--simple-fields-font-family` | font-size of field | sans-serif\n`--simple-fields-line-height` | line-height of field | 22px\n\n#### Detail text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-detail-font-size` | font-size of field details | 12px\n`--simple-fields-detail-font-family` | font-size of field details | sans-serif\n`--simple-fields-detail-line-height` | line-height of field details | 22px\n\n#### Disabled Fields\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-disabled-color` | disabled text color | #999\n`--simple-fields-disabled-opacity` | opacity for disabled field | 0.7\n\n#### Radio Buttons and Checkboxes\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-radio-option-display` | display label with field (flex) or above (block) | flex\n`--simple-fields-radio-option-flex-wrap` | allow radio options to wrap to next line | wrap\n\n### Configuring schemaConversion Property\nYou can customise elements from JSON schema conversion by setting `schemaConversion` property.\n```\ntype: { //For properties in \"this.schema\", define elements based on a property's \"type\"\nobject: { //Defines element used when property's \"type\" is an \"object\"\n format: { //Optional: define elements for \"object\" properties by \"format\"\n \"tabs\": { //Defines element used for object properties when \"format\" is \"tabs\"\n element: \"a11y-tabs\" //Element to create, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n descriptionProperty: \"description\" //Optional: element's property that sets its description, e.g. \"description\"\n descriptionSlot: \"description\" //Optional: element's slot that contains its description, e.g. \"description\"\n errorProperty: \"error\" //Optional: element's property that sets its error status, e.g. \"error\"\n errorChangedProperty: \"error\" //Optional: event element fires when error status changes, e.g. \"error-changed\"\n errorMessageProperty: \"errorMessage\" //Optional: element's property that sets its error message, e.g. \"errorMessage\"\n errorMessageSlot: \"errorMessage\" //Optional: element's slot that contains its error message, e.g. \"errorMessage\"\n labelProperty: \"label\" //Optional: element's property that sets its label, e.g. \"label\"\n labelSlot: \"label\" //Optional: element's slot that contains its label, e.g. \"label\"\n valueProperty: \"value\" //Optional: element's property that gets its value, e.g. \"value\" or \"checked\"\n setValueProperty: \"value\" //Optional: element's property that sets its value, e.g. \"value\" or \"checked\" (default is same as valueProperty)\n valueChangedProperty: \"value-changed\" //Optional: event element fires when value property changes, e.g. \"value-changed\" or \"click\"\n valueSlot: \"\" //Optional: element's slot that's used to set its value, e.g. \"\"\n description: \"\" //Optional: element that contains description, e.g. \"p\", \"span\", \"paper-tooltip\", etc.\n child: { //Optional: child elements to be appended\n element: \"a11y-tab\" //Optional: type of child element, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n attributes: { //Optional: sets child element's attributes based on this.schemaConversion\n disabled: true //Example: sets disabled to true \n } \n properties: { //Optional: sets child element's attributes based on this.schema properties\n icon: \"iconName\" //Example: sets child element's icon property to this.schema property's iconName \n }, \n slots: { //Optional: inserts schema properties in child element's slots\n label: \"label\", //Example: places schema property's label into child element's label slot\n \"\": \"description\" //Example: places schema property's description into child element's unnamed slot\n } \n },\n attributes: {},\n properties: {},\n slots: {}\n }\n },\n defaultSettings: { //Default element used for object properties\n element: \"\"\n label: \"\"\n description: \"\" \n attributes: {} \n properties: {} \n slots: {} \n }\n}\n}\n``` \n### Configuring fieldsConversion Property\nYou can customise fields to JSON schema conversion by setting `fieldsConversion` property.\n```\ndefaultSettings: { //default JSON schema type if no type is matched\ntype: \"string\" //sets JSON schema type to string\n},\ninputMethod: { //for fields in \"this.fields\", define elements based on a property's \"inputMethod\"\ncolorpicker: { //settings if inputMethod is color picker\n defaultSettings: { //default colorpicker settings\n type: \"string\", //sets JSON schema type to string\n format: \"color\" //sets JSON schema format to color\n }\n}\n}\n```", + "description": "`simple-fields`\nUses JSON Schema to display a series of fields\n\n### Styling\n`` provides following custom properties\nfor styling:\n\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-margin` | vertical margin around container | 16px\n`--simple-fields-margin-small` | smaller vertical margin above field itself | 8px\n`--simple-fields-border-radus` | default border-radius | 2px\n`--simple-fields-color` | text color | black\n`--simple-fields-error-color` | error text color | #b40000\n`--simple-fields-accent-color` | accent text/underline color | #3f51b5\n`--simple-fields-border-color` | border-/underline color | #999\n`--simple-fields-border-color-light` | used for range tracks | #ccc\n`--simple-fields-faded-error-color` | used for range tracks | #ffc0c0\n\n#### Field text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-font-size` | font-size of field | 16px\n`--simple-fields-font-family` | font-size of field | sans-serif\n`--simple-fields-line-height` | line-height of field | 22px\n\n#### Detail text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-detail-font-size` | font-size of field details | 12px\n`--simple-fields-detail-font-family` | font-size of field details | sans-serif\n`--simple-fields-detail-line-height` | line-height of field details | 22px\n\n#### Disabled Fields\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-disabled-color` | disabled text color | #999\n`--simple-fields-disabled-opacity` | opacity for disabled field | 0.7\n\n#### Radio Buttons and Checkboxes\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-radio-option-display` | display label with field (flex) or above (block) | flex\n`--simple-fields-radio-option-flex-wrap` | allow radio options to wrap to next line | wrap\n\n### Configuring schemaConversion Property\nYou can customise elements from JSON schema conversion by setting `schemaConversion` property.\n```\ntype: { //For properties in \"this.schema\", define elements based on a property's \"type\"\nobject: { //Defines element used when property's \"type\" is an \"object\"\n format: { //Optional: define elements for \"object\" properties by \"format\"\n \"tabs\": { //Defines element used for object properties when \"format\" is \"tabs\"\n element: \"a11y-tabs\" //Element to create, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n descriptionProperty: \"description\" //Optional: element's property that sets its description, e.g. \"description\"\n descriptionSlot: \"description\" //Optional: element's slot that contains its description, e.g. \"description\"\n errorProperty: \"error\" //Optional: element's property that sets its error status, e.g. \"error\"\n errorChangedProperty: \"error\" //Optional: event element fires when error status changes, e.g. \"error-changed\"\n errorMessageProperty: \"errorMessage\" //Optional: element's property that sets its error message, e.g. \"errorMessage\"\n errorMessageSlot: \"errorMessage\" //Optional: element's slot that contains its error message, e.g. \"errorMessage\"\n labelProperty: \"label\" //Optional: element's property that sets its label, e.g. \"label\"\n labelSlot: \"label\" //Optional: element's slot that contains its label, e.g. \"label\"\n valueProperty: \"value\" //Optional: element's property that gets its value, e.g. \"value\" or \"checked\"\n setValueProperty: \"value\" //Optional: element's property that sets its value, e.g. \"value\" or \"checked\" (default is same as valueProperty)\n valueChangedProperty: \"value-changed\" //Optional: event element fires when value property changes, e.g. \"value-changed\" or \"click\"\n valueSlot: \"\" //Optional: element's slot that's used to set its value, e.g. \"\"\n description: \"\" //Optional: element that contains description, e.g. \"p\", \"span\", etc.\n child: { //Optional: child elements to be appended\n element: \"a11y-tab\" //Optional: type of child element, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n attributes: { //Optional: sets child element's attributes based on this.schemaConversion\n disabled: true //Example: sets disabled to true \n } \n properties: { //Optional: sets child element's attributes based on this.schema properties\n icon: \"iconName\" //Example: sets child element's icon property to this.schema property's iconName \n }, \n slots: { //Optional: inserts schema properties in child element's slots\n label: \"label\", //Example: places schema property's label into child element's label slot\n \"\": \"description\" //Example: places schema property's description into child element's unnamed slot\n } \n },\n attributes: {},\n properties: {},\n slots: {}\n }\n },\n defaultSettings: { //Default element used for object properties\n element: \"\"\n label: \"\"\n description: \"\" \n attributes: {} \n properties: {} \n slots: {} \n }\n}\n}\n``` \n### Configuring fieldsConversion Property\nYou can customise fields to JSON schema conversion by setting `fieldsConversion` property.\n```\ndefaultSettings: { //default JSON schema type if no type is matched\ntype: \"string\" //sets JSON schema type to string\n},\ninputMethod: { //for fields in \"this.fields\", define elements based on a property's \"inputMethod\"\ncolorpicker: { //settings if inputMethod is color picker\n defaultSettings: { //default colorpicker settings\n type: \"string\", //sets JSON schema type to string\n format: \"color\" //sets JSON schema format to color\n }\n}\n}\n```", "name": "SimpleFields", "members": [ { @@ -13855,7 +13855,7 @@ "declarations": [ { "kind": "class", - "description": "`simple-fields-lite`\nUses JSON Schema of fields to display a series of fields\n\n### Styling\n`` provides following custom properties\nfor styling:\n\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-margin` | vertical margin around container | 16px\n`--simple-fields-margin-small` | smaller vertical margin above field itself | 8px\n`--simple-fields-border-radius` | default border-radius | 2px\n`--simple-fields-color` | text color | black\n`--simple-fields-background-color` | background color | transparent\n`--simple-fields-error-color` | error text color | #b40000\n`--simple-fields-accent-color` | accent text/underline color | #3f51b5\n`--simple-fields-border-color` | border-/underline color | #999\n\n#### Field text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-font-size` | font-size of field | 16px\n`--simple-fields-font-family` | font-size of field | sans-serif\n`--simple-fields-line-height` | line-height of field | 22px\n\n#### Detail text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-detail-font-size` | font-size of field details | 12px\n`--simple-fields-detail-font-family` | font-size of field details | sans-serif\n`--simple-fields-detail-line-height` | line-height of field details | 22px\n\n#### Disabled Fields\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-disabled-color` | disabled text color | #999\n`--simple-fields-disabled-opacity` | opacity for disabled field | 0.7\n\n### JSON Schema Format\nThis element accepts JSON schema with additional features noted in the example below:\n```\n{\n$schema: \"http://json-schema.org/schema#\",\ntitle: \"Store\",\ntype: \"object\",\nformat: \"tabs\", //default object behavior can be overridden by format\nrequired: [ \"name\", \"email\" ],\n properties: {\n settings: {\n title: \"Settings\",\n description: \"Configure the following.\",\n type: \"object\",\n format: \"tabs\",\n properties: {\n \"basic-input\": {\n title: \"Basic input page\",\n description: \"Basic contact settings\",\n type: \"object\",\n properties: {\n branch: {\n title: \"Branch\",\n type: \"string\"\n },\n name: {\n title: \"Name\",\n type: \"string\"\n },\n address: {\n title: \"Address\",\n type: \"string\",\n minLength: 3\n },\n city: {\n title: \"City\",\n type: \"string\",\n minLength: 3\n },\n province: {\n title: \"Province\",\n type: \"string\",\n minLength: 2\n },\n country: {\n title: \"Country\",\n type: \"string\",\n minLength: 2\n },\n postalCode: {\n title: \"Postal/Zip Code\",\n type: \"string\",\n pattern:\n \"[a-zA-Z][0-9][a-zA-Z]\\\\s*[0-9][a-zA-Z][0-9]|[0-9]{5}(-[0-9]{4})?\"\n },\n email: {\n title: \"Email\",\n type: \"string\",\n pattern:\n \"(?:^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,4}$)|(?:^$)\"\n },\n website: {\n title: \"Website\",\n type: \"string\",\n format: \"uri\"\n },\n establishedDate: {\n title: \"Established Date\",\n type: \"string\",\n format: \"date\"\n },\n closedDate: {\n title: \"Closed Date\",\n type: [\"string\", \"null\"],\n format: \"date\"\n }\n }\n },\n arrays: {\n title: \"Basic arrays page\",\n description: \"Demonstrates arrays\",\n type: \"object\",\n properties: {\n phoneNumbers: {\n title: \"Phone numbers\",\n description: \"List phone numbers and type of number.\",\n type: \"array\",\n items: {\n type: \"object\",\n previewBy: [\"phoneNumber\"], //simple-fields-array allows a preview field \n //for progressive disclosure of array items\n properties: {\n type: {\n title: \"Type\",\n type: \"string\"\n },\n phoneNumber: {\n title: \"Phone Number\",\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n```\n\n### Configuring schemaConversion Property\nYou can customise elements from JSON schema conversion by setting `schemaConversion` property.\n```\ntype: { //For properties in \"this.schema\", define elements based on a property's \"type\"\nobject: { //Defines element used when property's \"type\" is an \"object\"\n format: { //Optional: define elements for \"object\" properties by \"format\"\n \"tabs\": { //Defines element used for object properties when \"format\" is \"tabs\"\n element: \"a11y-tabs\" //Element to create, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n descriptionProperty: \"description\" //Optional: element's property that sets its description, e.g. \"description\"\n descriptionSlot: \"description\" //Optional: element's slot that contains its description, e.g. \"description\"\n errorProperty: \"error\" //Optional: element's property that sets its error status, e.g. \"error\"\n errorChangedProperty: \"error\" //Optional: event element fires when error status changes, e.g. \"error-changed\"\n errorMessageProperty: \"errorMessage\" //Optional: element's property that sets its error message, e.g. \"errorMessage\"\n errorMessageSlot: \"errorMessage\" //Optional: element's slot that contains its error message, e.g. \"errorMessage\"\n labelProperty: \"label\" //Optional: element's property that sets its label, e.g. \"label\"\n labelSlot: \"label\" //Optional: element's slot that contains its label, e.g. \"label\"\n prefixSlot: \"prefix\" //Optional: element's slot that contains its prefix, e.g. \"prefix\"\n suffixSlot: \"suffix\" //Optional: element's slot that contains its suffix, e.g. \"suffix\"\n valueProperty: \"value\" //Optional: element's property that gets its value, e.g. \"value\" or \"checked\"\n setValueProperty: \"value\" //Optional: element's property that sets its value, e.g. \"value\" or \"checked\" (default is same as valueProperty)\n valueChangedProperty: \"value-changed\" //Optional: event element fires when value property changes, e.g. \"value-changed\" or \"click\"\n valueSlot: \"\" //Optional: element's slot that's used to set its value, e.g. \"\"\n description: \"\" //Optional: element that contains description, e.g. \"p\", \"span\", \"paper-tooltip\", etc.\n child: { //Optional: child elements to be appended\n element: \"a11y-tab\" //Optional: type of child element, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n attributes: { //Optional: sets child element's attributes based on this.schemaConversion\n disabled: true //Example: sets disabled to true \n } \n properties: { //Optional: sets child element's attributes based on this.schema properties\n icon: \"iconName\" //Example: sets child element's icon property to this.schema property's iconName \n }, \n slots: { //Optional: inserts schema properties in child element's slots\n label: \"label\", //Example: places schema property's label into child element's label slot\n \"\": \"description\" //Example: places schema property's description into child element's unnamed slot\n } \n },\n attributes: {},\n properties: {},\n slots: {}\n }\n },\n defaultSettings: { //Default element used for object properties\n element: \"\"\n label: \"\"\n description: \"\" \n attributes: {} \n properties: {} \n slots: {} \n }\n}\n}\n```", + "description": "`simple-fields-lite`\nUses JSON Schema of fields to display a series of fields\n\n### Styling\n`` provides following custom properties\nfor styling:\n\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-margin` | vertical margin around container | 16px\n`--simple-fields-margin-small` | smaller vertical margin above field itself | 8px\n`--simple-fields-border-radius` | default border-radius | 2px\n`--simple-fields-color` | text color | black\n`--simple-fields-background-color` | background color | transparent\n`--simple-fields-error-color` | error text color | #b40000\n`--simple-fields-accent-color` | accent text/underline color | #3f51b5\n`--simple-fields-border-color` | border-/underline color | #999\n\n#### Field text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-font-size` | font-size of field | 16px\n`--simple-fields-font-family` | font-size of field | sans-serif\n`--simple-fields-line-height` | line-height of field | 22px\n\n#### Detail text\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-detail-font-size` | font-size of field details | 12px\n`--simple-fields-detail-font-family` | font-size of field details | sans-serif\n`--simple-fields-detail-line-height` | line-height of field details | 22px\n\n#### Disabled Fields\nCustom property | Description | Default\n----------------|-------------|--------\n`--simple-fields-disabled-color` | disabled text color | #999\n`--simple-fields-disabled-opacity` | opacity for disabled field | 0.7\n\n### JSON Schema Format\nThis element accepts JSON schema with additional features noted in the example below:\n```\n{\n$schema: \"http://json-schema.org/schema#\",\ntitle: \"Store\",\ntype: \"object\",\nformat: \"tabs\", //default object behavior can be overridden by format\nrequired: [ \"name\", \"email\" ],\n properties: {\n settings: {\n title: \"Settings\",\n description: \"Configure the following.\",\n type: \"object\",\n format: \"tabs\",\n properties: {\n \"basic-input\": {\n title: \"Basic input page\",\n description: \"Basic contact settings\",\n type: \"object\",\n properties: {\n branch: {\n title: \"Branch\",\n type: \"string\"\n },\n name: {\n title: \"Name\",\n type: \"string\"\n },\n address: {\n title: \"Address\",\n type: \"string\",\n minLength: 3\n },\n city: {\n title: \"City\",\n type: \"string\",\n minLength: 3\n },\n province: {\n title: \"Province\",\n type: \"string\",\n minLength: 2\n },\n country: {\n title: \"Country\",\n type: \"string\",\n minLength: 2\n },\n postalCode: {\n title: \"Postal/Zip Code\",\n type: \"string\",\n pattern:\n \"[a-zA-Z][0-9][a-zA-Z]\\\\s*[0-9][a-zA-Z][0-9]|[0-9]{5}(-[0-9]{4})?\"\n },\n email: {\n title: \"Email\",\n type: \"string\",\n pattern:\n \"(?:^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,4}$)|(?:^$)\"\n },\n website: {\n title: \"Website\",\n type: \"string\",\n format: \"uri\"\n },\n establishedDate: {\n title: \"Established Date\",\n type: \"string\",\n format: \"date\"\n },\n closedDate: {\n title: \"Closed Date\",\n type: [\"string\", \"null\"],\n format: \"date\"\n }\n }\n },\n arrays: {\n title: \"Basic arrays page\",\n description: \"Demonstrates arrays\",\n type: \"object\",\n properties: {\n phoneNumbers: {\n title: \"Phone numbers\",\n description: \"List phone numbers and type of number.\",\n type: \"array\",\n items: {\n type: \"object\",\n previewBy: [\"phoneNumber\"], //simple-fields-array allows a preview field \n //for progressive disclosure of array items\n properties: {\n type: {\n title: \"Type\",\n type: \"string\"\n },\n phoneNumber: {\n title: \"Phone Number\",\n type: \"string\"\n }\n }\n }\n }\n }\n }\n }\n }\n }\n}\n```\n\n### Configuring schemaConversion Property\nYou can customise elements from JSON schema conversion by setting `schemaConversion` property.\n```\ntype: { //For properties in \"this.schema\", define elements based on a property's \"type\"\nobject: { //Defines element used when property's \"type\" is an \"object\"\n format: { //Optional: define elements for \"object\" properties by \"format\"\n \"tabs\": { //Defines element used for object properties when \"format\" is \"tabs\"\n element: \"a11y-tabs\" //Element to create, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n descriptionProperty: \"description\" //Optional: element's property that sets its description, e.g. \"description\"\n descriptionSlot: \"description\" //Optional: element's slot that contains its description, e.g. \"description\"\n errorProperty: \"error\" //Optional: element's property that sets its error status, e.g. \"error\"\n errorChangedProperty: \"error\" //Optional: event element fires when error status changes, e.g. \"error-changed\"\n errorMessageProperty: \"errorMessage\" //Optional: element's property that sets its error message, e.g. \"errorMessage\"\n errorMessageSlot: \"errorMessage\" //Optional: element's slot that contains its error message, e.g. \"errorMessage\"\n labelProperty: \"label\" //Optional: element's property that sets its label, e.g. \"label\"\n labelSlot: \"label\" //Optional: element's slot that contains its label, e.g. \"label\"\n prefixSlot: \"prefix\" //Optional: element's slot that contains its prefix, e.g. \"prefix\"\n suffixSlot: \"suffix\" //Optional: element's slot that contains its suffix, e.g. \"suffix\"\n valueProperty: \"value\" //Optional: element's property that gets its value, e.g. \"value\" or \"checked\"\n setValueProperty: \"value\" //Optional: element's property that sets its value, e.g. \"value\" or \"checked\" (default is same as valueProperty)\n valueChangedProperty: \"value-changed\" //Optional: event element fires when value property changes, e.g. \"value-changed\" or \"click\"\n valueSlot: \"\" //Optional: element's slot that's used to set its value, e.g. \"\"\n description: \"\" //Optional: element that contains description, e.g. \"p\", \"span\", etc.\n child: { //Optional: child elements to be appended\n element: \"a11y-tab\" //Optional: type of child element, eg. \"paper-input\", \"select\", \"simple-fields-array\", etc.\n attributes: { //Optional: sets child element's attributes based on this.schemaConversion\n disabled: true //Example: sets disabled to true \n } \n properties: { //Optional: sets child element's attributes based on this.schema properties\n icon: \"iconName\" //Example: sets child element's icon property to this.schema property's iconName \n }, \n slots: { //Optional: inserts schema properties in child element's slots\n label: \"label\", //Example: places schema property's label into child element's label slot\n \"\": \"description\" //Example: places schema property's description into child element's unnamed slot\n } \n },\n attributes: {},\n properties: {},\n slots: {}\n }\n },\n defaultSettings: { //Default element used for object properties\n element: \"\"\n label: \"\"\n description: \"\" \n attributes: {} \n properties: {} \n slots: {} \n }\n}\n}\n```", "name": "SimpleFieldsLite", "members": [ { diff --git a/elements/simple-fields/demo/conditional.html b/elements/simple-fields/demo/conditional.html index 8202040698..39cfaa6429 100644 --- a/elements/simple-fields/demo/conditional.html +++ b/elements/simple-fields/demo/conditional.html @@ -10,10 +10,10 @@ - +
      diff --git a/elements/simple-fields/demo/container.html b/elements/simple-fields/demo/container.html index 1344aad81b..e93a189d2a 100644 --- a/elements/simple-fields/demo/container.html +++ b/elements/simple-fields/demo/container.html @@ -10,10 +10,10 @@ - +
      diff --git a/elements/simple-fields/demo/field.html b/elements/simple-fields/demo/field.html index e743d037b7..34204c06e0 100644 --- a/elements/simple-fields/demo/field.html +++ b/elements/simple-fields/demo/field.html @@ -10,7 +10,7 @@ - +
      diff --git a/elements/simple-fields/demo/form-lite.html b/elements/simple-fields/demo/form-lite.html index bfbd464469..4a4bd97fb1 100644 --- a/elements/simple-fields/demo/form-lite.html +++ b/elements/simple-fields/demo/form-lite.html @@ -10,10 +10,10 @@ - +
      diff --git a/elements/simple-fields/demo/form.html b/elements/simple-fields/demo/form.html index 96a7372c60..e33dcea6df 100644 --- a/elements/simple-fields/demo/form.html +++ b/elements/simple-fields/demo/form.html @@ -9,10 +9,10 @@ - +
      diff --git a/elements/simple-fields/demo/index.html b/elements/simple-fields/demo/index.html index 4e85e4dac1..c0c1afffc7 100755 --- a/elements/simple-fields/demo/index.html +++ b/elements/simple-fields/demo/index.html @@ -10,14 +10,14 @@ - +
      diff --git a/elements/simple-fields/demo/lite.html b/elements/simple-fields/demo/lite.html index cbaf102609..eec30b8e64 100644 --- a/elements/simple-fields/demo/lite.html +++ b/elements/simple-fields/demo/lite.html @@ -10,10 +10,10 @@ - +
      diff --git a/elements/simple-fields/demo/schema.html b/elements/simple-fields/demo/schema.html index 000ff53c22..c96ee76488 100644 --- a/elements/simple-fields/demo/schema.html +++ b/elements/simple-fields/demo/schema.html @@ -10,10 +10,10 @@ - +
      diff --git a/elements/simple-fields/demo/subschema.html b/elements/simple-fields/demo/subschema.html index 1e23c5eef1..9efdc250b8 100644 --- a/elements/simple-fields/demo/subschema.html +++ b/elements/simple-fields/demo/subschema.html @@ -10,10 +10,10 @@ - +
      diff --git a/elements/simple-fields/demo/tags.html b/elements/simple-fields/demo/tags.html index f33acb6445..2c9608de13 100755 --- a/elements/simple-fields/demo/tags.html +++ b/elements/simple-fields/demo/tags.html @@ -10,7 +10,7 @@ +
      diff --git a/elements/simple-fields/index.html b/elements/simple-fields/index.html index 8857fd3bc1..545b460056 100755 --- a/elements/simple-fields/index.html +++ b/elements/simple-fields/index.html @@ -4,10 +4,10 @@ simple-fields documentation - - + + - + diff --git a/elements/simple-fields/lib/simple-context-menu.js b/elements/simple-fields/lib/simple-context-menu.js new file mode 100644 index 0000000000..a0ca3bd01f --- /dev/null +++ b/elements/simple-fields/lib/simple-context-menu.js @@ -0,0 +1,307 @@ +/** + * Copyright 2025 The Pennsylvania State University + * @license Apache-2.0, see License.md for full text. + */ +import { html, css } from "lit"; +import { DDD } from "@haxtheweb/d-d-d/d-d-d.js"; + +/** + * `simple-context-menu` + * `Reusable context menu dialog with keyboard navigation and accessibility` + * + * @demo demo/index.html + * @element simple-context-menu + */ +class SimpleContextMenu extends DDD { + static get tag() { + return "simple-context-menu"; + } + + static get styles() { + return [ + super.styles, + css` + :host { + display: inline-block; + --simple-toolbar-border-radius: 0; + } + + dialog { + position: absolute; + background: light-dark( + var(--simple-context-menu-background, white), + var(--simple-context-menu-background-dark, #000000) + ); + border: var(--ddd-border-sm); + border-color: light-dark( + var(--simple-context-menu-border-color, #e0e0e0), + var(--simple-context-menu-border-color-dark, #444) + ); + box-shadow: var( + --simple-context-menu-shadow, + 0 2px 8px rgba(0, 0, 0, 0.15) + ); + min-width: var(--simple-context-menu-min-width, 200px); + padding: 0; + margin: 0; + z-index: var(--simple-context-menu-z-index, 1000); + border-radius: var(--ddd-radius-sm); + } + + dialog::backdrop { + background: transparent; + } + + .menu-header { + padding: var(--ddd-spacing-2); + font-weight: var(--ddd-font-weight-bold); + font-size: var(--ddd-font-size-4xs); + text-transform: uppercase; + border-bottom: var(--ddd-border-sm); + margin-bottom: var(--ddd-spacing-1); + color: var(--simple-context-menu-header-color, inherit); + } + + ::slotted(*) { + display: flex; + align-items: center; + gap: var(--ddd-spacing-1); + cursor: pointer; + border: none; + background: transparent; + text-align: left; + font-size: 12px; + color: inherit; + } + + ::slotted(*:hover), + ::slotted(*:focus) { + background: light-dark(#f0f0f0, #333); + outline: none; + } + + ::slotted([disabled]) { + opacity: 0.5; + cursor: not-allowed; + pointer-events: none; + } + `, + ]; + } + + static get properties() { + return { + ...super.properties, + /** + * Title displayed in menu header + */ + title: { + type: String, + }, + /** + * Whether the menu is open + */ + open: { + type: Boolean, + reflect: true, + }, + /** + * Position strategy: 'anchor' (relative to trigger), 'fixed' (custom positioning) + */ + positionStrategy: { + type: String, + attribute: "position-strategy", + }, + /** + * X coordinate for fixed positioning + */ + x: { + type: Number, + }, + /** + * Y coordinate for fixed positioning + */ + y: { + type: Number, + }, + /** + * Offset from anchor element (in pixels) + */ + offset: { + type: Number, + }, + }; + } + + constructor() { + super(); + this.title = ""; + this.open = false; + this.positionStrategy = "anchor"; + this.x = 0; + this.y = 0; + this.offset = 4; + this._anchorElement = null; + } + + render() { + return html` + + ${this.title + ? html`
      ${this.title}
      ` + : ""} + +
      + `; + } + + /** + * Toggle menu visibility + * @param {Element} anchorElement - Element to position menu relative to + */ + toggle(anchorElement = null) { + if (this.open) { + this.close(); + } else { + this.openMenu(anchorElement); + } + } + + /** + * Open the menu + * @param {Element} anchorElement - Element to position menu relative to + */ + openMenu(anchorElement = null) { + const dialog = this.shadowRoot.querySelector("#menu"); + if (!dialog) return; + + this._anchorElement = anchorElement; + dialog.showModal(); + this.open = true; + + // Position the dialog + this._positionDialog(dialog, anchorElement); + + // Focus first focusable item + setTimeout(() => { + const firstItem = this._getFocusableItems()[0]; + if (firstItem) firstItem.focus(); + }, 0); + + this.dispatchEvent( + new CustomEvent("simple-context-menu-opened", { + bubbles: true, + composed: true, + }), + ); + } + + /** + * Close the menu + */ + close() { + const dialog = this.shadowRoot.querySelector("#menu"); + if (dialog && dialog.open) { + dialog.close(); + this.open = false; + this.dispatchEvent( + new CustomEvent("simple-context-menu-closed", { + bubbles: true, + composed: true, + }), + ); + } + } + + /** + * Position the dialog based on strategy + */ + _positionDialog(dialog, anchorElement) { + if (this.positionStrategy === "fixed") { + dialog.style.top = `${this.y}px`; + dialog.style.left = `${this.x}px`; + } else if (this.positionStrategy === "anchor" && anchorElement) { + const rect = anchorElement.getBoundingClientRect(); + dialog.style.top = `${rect.bottom + this.offset}px`; + dialog.style.left = `${rect.left}px`; + } + } + + /** + * Get all focusable items in the menu + */ + _getFocusableItems() { + const slot = this.shadowRoot.querySelector("slot"); + const nodes = slot.assignedElements(); + return nodes.filter( + (node) => + !node.hasAttribute("disabled") && + (node.tagName === "BUTTON" || + node.tabIndex >= 0 || + node.hasAttribute("tabindex")), + ); + } + + /** + * Handle keyboard navigation + */ + _handleKeydown(e) { + const menuItems = this._getFocusableItems(); + if (menuItems.length === 0) return; + + const currentIndex = menuItems.indexOf(globalThis.document.activeElement); + + switch (e.key) { + case "ArrowDown": + e.preventDefault(); + const nextIndex = (currentIndex + 1) % menuItems.length; + if (menuItems[nextIndex]) { + menuItems[nextIndex].focus(); + } + break; + case "ArrowUp": + e.preventDefault(); + const prevIndex = + currentIndex === 0 ? menuItems.length - 1 : currentIndex - 1; + if (menuItems[prevIndex]) { + menuItems[prevIndex].focus(); + } + break; + case "Escape": + this.close(); + break; + case "Enter": + case " ": + e.preventDefault(); + if (currentIndex >= 0 && menuItems[currentIndex]) { + menuItems[currentIndex].click(); + } + break; + } + } + + /** + * Handle backdrop click to close + */ + _handleBackdropClick(e) { + const dialog = e.target; + if (e.target.nodeName === "DIALOG") { + const rect = dialog.getBoundingClientRect(); + if ( + e.clientX < rect.left || + e.clientX > rect.right || + e.clientY < rect.top || + e.clientY > rect.bottom + ) { + this.close(); + } + } + } +} + +globalThis.customElements.define(SimpleContextMenu.tag, SimpleContextMenu); +export { SimpleContextMenu }; diff --git a/elements/simple-fields/lib/simple-fields-field.js b/elements/simple-fields/lib/simple-fields-field.js index adac0180e9..ea25af0a6f 100644 --- a/elements/simple-fields/lib/simple-fields-field.js +++ b/elements/simple-fields/lib/simple-fields-field.js @@ -83,6 +83,10 @@ const SimpleFieldsFieldBehaviors = function (SuperClass) { --simple-fields-background-color, transparent ); + color: light-dark( + var(--simple-fields-color, var(--ddd-theme-default-coalyGray, #262626)), + var(--simple-fields-color, var(--ddd-theme-default-limestoneLight, #e4e5e7)) + ); } input[type="text"] { padding: 0; @@ -96,6 +100,10 @@ const SimpleFieldsFieldBehaviors = function (SuperClass) { --simple-fields-background-color, transparent ); + color: light-dark( + var(--simple-fields-color, var(--ddd-theme-default-coalyGray, #262626)), + var(--simple-fields-color, var(--ddd-theme-default-limestoneLight, #e4e5e7)) + ); } input::placeholder, textarea::placeholder { @@ -134,9 +142,9 @@ const SimpleFieldsFieldBehaviors = function (SuperClass) { var(--ddd-font-weight-regular, 400) ); font-style: normal; - color: var( - --simple-fields-color, - var(--ddd-theme-default-coalyGray, #262626) + color: light-dark( + var(--simple-fields-color, var(--ddd-theme-default-coalyGray, #262626)), + var(--simple-fields-color, var(--ddd-theme-default-limestoneLight, #e4e5e7)) ); } select.field { @@ -152,6 +160,10 @@ const SimpleFieldsFieldBehaviors = function (SuperClass) { -moz-appearance: none; appearance: none; cursor: pointer; + color: light-dark( + var(--simple-fields-color, var(--ddd-theme-default-coalyGray, #262626)), + var(--simple-fields-color, var(--ddd-theme-default-limestoneLight, #e4e5e7)) + ); } :host([type="select"]) { cursor: pointer; diff --git a/elements/simple-fields/lib/simple-fields-lite.js b/elements/simple-fields/lib/simple-fields-lite.js index 65d115fddf..5ed554e36a 100755 --- a/elements/simple-fields/lib/simple-fields-lite.js +++ b/elements/simple-fields/lib/simple-fields-lite.js @@ -178,7 +178,7 @@ type: { //For properties in "this.schema", setValueProperty: "value" //Optional: element's property that sets its value, e.g. "value" or "checked" (default is same as valueProperty) valueChangedProperty: "value-changed" //Optional: event element fires when value property changes, e.g. "value-changed" or "click" valueSlot: "" //Optional: element's slot that's used to set its value, e.g. "" - description: "" //Optional: element that contains description, e.g. "p", "span", "paper-tooltip", etc. + description: "" //Optional: element that contains description, e.g. "p", "span", etc. child: { //Optional: child elements to be appended element: "a11y-tab" //Optional: type of child element, eg. "paper-input", "select", "simple-fields-array", etc. attributes: { //Optional: sets child element's attributes based on this.schemaConversion diff --git a/elements/simple-fields/lib/simple-fields-upload.js b/elements/simple-fields/lib/simple-fields-upload.js index 84ae443148..4f6e7e5379 100644 --- a/elements/simple-fields/lib/simple-fields-upload.js +++ b/elements/simple-fields/lib/simple-fields-upload.js @@ -37,7 +37,10 @@ class SimpleFieldsUpload extends I18NMixin( pointer-events: all; overflow: visible; --simple-login-camera-aspect: 1.777777777777; - --simple-camera-snap-color: var(--ddd-theme-default-coalyGray, currentColor); + --simple-camera-snap-color: var( + --ddd-theme-default-coalyGray, + currentColor + ); --simple-camera-snap-background: var( --ddd-theme-default-white, white @@ -45,7 +48,10 @@ class SimpleFieldsUpload extends I18NMixin( --simple-camera-snap-border-radius: var(--ddd-radius-sm); --lumo-font-family: var(--ddd-font-navigation, sans-serif); --lumo-error-color: var(--ddd-theme-default-error, #b40000); - --lumo-primary-font-color: var(--ddd-theme-default-coalyGray, currentColor); + --lumo-primary-font-color: var( + --ddd-theme-default-coalyGray, + currentColor + ); --lumo-base-color: var(--ddd-theme-default-white, white); } :host([responsive-size="xs"]), @@ -71,7 +77,8 @@ class SimpleFieldsUpload extends I18NMixin( } #upload { border-radius: var(--ddd-radius-sm, 2px); - border: var(--ddd-border-sm) dashed var(--ddd-theme-default-limestoneGray, #ccc); + border: var(--ddd-border-sm) dashed + var(--ddd-theme-default-limestoneGray, #ccc); } #url { flex: 1 1 100%; @@ -87,7 +94,7 @@ class SimpleFieldsUpload extends I18NMixin( simple-fields-url-combo[always-expanded]::part(listbox) { background-color: transparent; } - + simple-toolbar-button { display: inline-flex; font-family: var(--ddd-font-navigation, sans-serif); @@ -130,7 +137,7 @@ class SimpleFieldsUpload extends I18NMixin( vaadin-upload[dragover] { border-color: var( --simple-fields-secondary-accent-color, - var(--simple-colors-default-theme-accent-3, #77e2ff) + var(--ddd-theme-default-skyBlue,#009dc7) ); } vaadin-upload::part(drop-label) { @@ -156,9 +163,15 @@ class SimpleFieldsUpload extends I18NMixin( --lumo-contrast-90pct: currentColor; } simple-camera-snap { - --simple-camera-snap-button-container-bottom: var(--ddd-spacing-1, 4px); + --simple-camera-snap-button-container-bottom: var( + --ddd-spacing-1, + 4px + ); --simple-camera-snap-button-container-z-index: 5; - --simple-camera-snap-button-border-radius: var(--ddd-radius-rounded, 100%); + --simple-camera-snap-button-border-radius: var( + --ddd-radius-rounded, + 100% + ); --simple-camera-snap-button-opacity: 0.8; max-width: 200px; margin: 0 auto; @@ -166,7 +179,8 @@ class SimpleFieldsUpload extends I18NMixin( /** voice stuff which is in lite dom below */ .vmsg-button { - border: var(--ddd-border-sm) solid var(--ddd-theme-default-limestoneGray, #ccc); + border: var(--ddd-border-sm) solid + var(--ddd-theme-default-limestoneGray, #ccc); border-radius: var(--ddd-radius-sm, 4px); padding: var(--ddd-spacing-1, 4px) var(--ddd-spacing-2, 8px); margin: 0 var(--ddd-spacing-1, 4px); @@ -367,9 +381,7 @@ class SimpleFieldsUpload extends I18NMixin(
      ${this.t.dropMediaHereOr} -
      - ${this.sources} -
      +
      ${this.sources}
      SimpleFilter: simple-filter Demo - - +
      diff --git a/elements/simple-filter/index.html b/elements/simple-filter/index.html index 0173efbc76..c7d86b4cab 100644 --- a/elements/simple-filter/index.html +++ b/elements/simple-filter/index.html @@ -4,10 +4,10 @@ simple-filter documentation - - + + - + diff --git a/elements/simple-filter/package.json b/elements/simple-filter/package.json index 043aecf45b..6b39347243 100644 --- a/elements/simple-filter/package.json +++ b/elements/simple-filter/package.json @@ -43,16 +43,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-icon-picker/.gitignore b/elements/simple-icon-picker/.gitignore index bddbc94db5..5456ae68cd 100755 --- a/elements/simple-icon-picker/.gitignore +++ b/elements/simple-icon-picker/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-icon-picker/custom-elements.json b/elements/simple-icon-picker/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/simple-icon-picker/demo/index.html b/elements/simple-icon-picker/demo/index.html index 124d6b8819..ca34299fc1 100755 --- a/elements/simple-icon-picker/demo/index.html +++ b/elements/simple-icon-picker/demo/index.html @@ -9,10 +9,10 @@ - +
      diff --git a/elements/simple-icon-picker/index.html b/elements/simple-icon-picker/index.html index fe5d919bd3..213c76a8b2 100755 --- a/elements/simple-icon-picker/index.html +++ b/elements/simple-icon-picker/index.html @@ -4,10 +4,10 @@ simple-icon-picker documentation - - + + - + diff --git a/elements/simple-icon-picker/package.json b/elements/simple-icon-picker/package.json old mode 100755 new mode 100644 index 3ee90692bd..c910b5fbd7 --- a/elements/simple-icon-picker/package.json +++ b/elements/simple-icon-picker/package.json @@ -43,17 +43,14 @@ "dependencies": { "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/simple-picker": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@haxtheweb/hax-iconset": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-icon/.gitignore b/elements/simple-icon/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-icon/.gitignore +++ b/elements/simple-icon/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-icon/custom-elements.json b/elements/simple-icon/custom-elements.json index ce2a5c1861..e7daae6f1a 100644 --- a/elements/simple-icon/custom-elements.json +++ b/elements/simple-icon/custom-elements.json @@ -224,8 +224,8 @@ } ], "superclass": { - "name": "DDD", - "package": "@haxtheweb/d-d-d" + "name": "SimpleColors", + "package": "@haxtheweb/simple-colors/simple-colors.js" }, "tagName": "simple-icon", "customElement": true diff --git a/elements/simple-icon/demo/button-lite.html b/elements/simple-icon/demo/button-lite.html index 1029f0e493..0e5c4d1a9a 100644 --- a/elements/simple-icon/demo/button-lite.html +++ b/elements/simple-icon/demo/button-lite.html @@ -4,18 +4,17 @@ SimpleIcon: simple-icon Demo - - +
      diff --git a/elements/simple-icon/demo/button.html b/elements/simple-icon/demo/button.html index ff257ab191..80224c0123 100644 --- a/elements/simple-icon/demo/button.html +++ b/elements/simple-icon/demo/button.html @@ -4,18 +4,17 @@ SimpleIcon: simple-icon Demo - - +
      diff --git a/elements/simple-icon/demo/iconset.html b/elements/simple-icon/demo/iconset.html index 07f30de00f..dd2d720d2d 100644 --- a/elements/simple-icon/demo/iconset.html +++ b/elements/simple-icon/demo/iconset.html @@ -4,15 +4,14 @@ SimpleIcon: simple-icon Demo - - +
      diff --git a/elements/simple-icon/demo/index.html b/elements/simple-icon/demo/index.html index 703b0b80fe..4b6618fbad 100644 --- a/elements/simple-icon/demo/index.html +++ b/elements/simple-icon/demo/index.html @@ -4,18 +4,17 @@ SimpleIcon: simple-icon Demo - - + +
      diff --git a/elements/simple-icon/index.html b/elements/simple-icon/index.html index 5a994e6f01..fdd4f662cf 100644 --- a/elements/simple-icon/index.html +++ b/elements/simple-icon/index.html @@ -4,10 +4,10 @@ simple-icon documentation - - + + - + diff --git a/elements/simple-icon/lib/simple-icon.haxProperties.json b/elements/simple-icon/lib/simple-icon.haxProperties.json index 7bec6145af..bd0da210b0 100644 --- a/elements/simple-icon/lib/simple-icon.haxProperties.json +++ b/elements/simple-icon/lib/simple-icon.haxProperties.json @@ -13,7 +13,8 @@ "handles": [], "meta": { "author": "HAXTheWeb core team", - "inlineOnly": true + "inlineOnly": true, + "selectionRequired": false } }, "settings": { diff --git a/elements/simple-icon/lib/simple-iconset-demo.js b/elements/simple-icon/lib/simple-iconset-demo.js index 5bf2391d03..21c00a1ef4 100644 --- a/elements/simple-icon/lib/simple-iconset-demo.js +++ b/elements/simple-icon/lib/simple-iconset-demo.js @@ -5,7 +5,6 @@ import { html, svg, css, LitElement } from "lit"; import "./simple-icon-lite.js"; import "./simple-icons.js"; -import { SimpleIconsetStore } from "./simple-iconset.js"; import { SimpleIconIconsetsManifest } from "./simple-iconset-manifest.js"; import { HaxIconsetManifest } from "@haxtheweb/hax-iconset/lib/hax-iconset-manifest.js"; diff --git a/elements/simple-icon/lib/simple-iconset-manifest.js b/elements/simple-icon/lib/simple-iconset-manifest.js index aa1d3bb45d..f32fb119e3 100644 --- a/elements/simple-icon/lib/simple-iconset-manifest.js +++ b/elements/simple-icon/lib/simple-iconset-manifest.js @@ -1,12 +1,12 @@ import { SimpleIconsetStore } from "@haxtheweb/simple-icon/lib/simple-iconset.js"; /** - * @const SimpleIconIconsetsManifest + * @const SimpleIconIconsetsManifest */ export const SimpleIconIconsetsManifest = [ { - "name": "av", - "icons": [ + name: "av", + icons: [ "add-to-queue", "airplay", "album", @@ -86,12 +86,12 @@ export const SimpleIconIconsetsManifest = [ "volume-off", "volume-up", "web-asset", - "web" - ] + "web", + ], }, { - "name": "communication", - "icons": [ + name: "communication", + icons: [ "business", "call-end", "call-made", @@ -141,12 +141,12 @@ export const SimpleIconIconsetsManifest = [ "swap-calls", "textsms", "voicemail", - "vpn-key" - ] + "vpn-key", + ], }, { - "name": "device", - "icons": [ + name: "device", + icons: [ "access-alarm", "access-alarms", "access-time", @@ -225,12 +225,12 @@ export const SimpleIconIconsetsManifest = [ "wallpaper", "widgets", "wifi-lock", - "wifi-tethering" - ] + "wifi-tethering", + ], }, { - "name": "editor", - "icons": [ + name: "editor", + icons: [ "attach-file", "attach-money", "border-all", @@ -299,16 +299,16 @@ export const SimpleIconIconsetsManifest = [ "vertical-align-bottom", "vertical-align-center", "vertical-align-top", - "wrap-text" - ] + "wrap-text", + ], }, { - "name": "elmsln-custom", - "icons": [] + name: "elmsln-custom", + icons: [], }, { - "name": "hardware", - "icons": [ + name: "hardware", + icons: [ "cast-connected", "cast", "computer", @@ -356,12 +356,12 @@ export const SimpleIconIconsetsManifest = [ "toys", "tv", "videogame-asset", - "watch" - ] + "watch", + ], }, { - "name": "icons", - "icons": [ + name: "icons", + icons: [ "3d-rotation", "accessibility", "accessible", @@ -670,12 +670,12 @@ export const SimpleIconIconsetsManifest = [ "work", "youtube-searched-for", "zoom-in", - "zoom-out" - ] + "zoom-out", + ], }, { - "name": "image", - "icons": [ + name: "image", + icons: [ "add-a-photo", "add-to-photos", "adjust", @@ -830,18 +830,16 @@ export const SimpleIconIconsetsManifest = [ "wb-cloudy", "wb-incandescent", "wb-iridescent", - "wb-sunny" - ] + "wb-sunny", + ], }, { - "name": "loading", - "icons": [ - "bars" - ] + name: "loading", + icons: ["bars"], }, { - "name": "maps", - "icons": [ + name: "maps", + icons: [ "add-location", "beenhere", "directions-bike", @@ -909,12 +907,12 @@ export const SimpleIconIconsetsManifest = [ "train", "tram", "transfer-within-a-station", - "zoom-out-map" - ] + "zoom-out-map", + ], }, { - "name": "notification", - "icons": [ + name: "notification", + icons: [ "adb", "airline-seat-flat-angled", "airline-seat-flat", @@ -968,12 +966,12 @@ export const SimpleIconIconsetsManifest = [ "voice-chat", "vpn-lock", "wc", - "wifi" - ] + "wifi", + ], }, { - "name": "places", - "icons": [ + name: "places", + icons: [ "ac-unit", "airport-shuttle", "all-inclusive", @@ -992,12 +990,12 @@ export const SimpleIconIconsetsManifest = [ "rv-hookup", "smoke-free", "smoking-rooms", - "spa" - ] + "spa", + ], }, { - "name": "social", - "icons": [ + name: "social", + icons: [ "cake", "domain", "group-add", @@ -1027,8 +1025,8 @@ export const SimpleIconIconsetsManifest = [ "sentiment-very-dissatisfied", "sentiment-very-satisfied", "share", - "whatshot" - ] - } + "whatshot", + ], + }, ]; -SimpleIconsetStore.registerManifest(SimpleIconIconsetsManifest); \ No newline at end of file +SimpleIconsetStore.registerManifest(SimpleIconIconsetsManifest); diff --git a/elements/simple-icon/package.json b/elements/simple-icon/package.json index b7a16985c6..67fe4604cc 100644 --- a/elements/simple-icon/package.json +++ b/elements/simple-icon/package.json @@ -46,19 +46,15 @@ }, "license": "Apache-2.0", "dependencies": { - "@haxtheweb/d-d-d": "^11.0.5", "@haxtheweb/simple-colors": "^11.0.5", "flag-icons": "6.6.4", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-icon/simple-icon.js b/elements/simple-icon/simple-icon.js index eaedde3264..5565b5c42e 100644 --- a/elements/simple-icon/simple-icon.js +++ b/elements/simple-icon/simple-icon.js @@ -5,7 +5,6 @@ import { svg, css } from "lit"; import { SimpleColors } from "@haxtheweb/simple-colors/simple-colors.js"; import { SimpleIconBehaviors } from "./lib/simple-icon-lite.js"; -import { DDD } from "@haxtheweb/d-d-d"; /** * `simple-icon` * `Render an SVG based icon` @@ -22,7 +21,7 @@ import { DDD } from "@haxtheweb/d-d-d"; * @demo demo/iconset.html Iconset Demo * @element simple-icon */ -class SimpleIcon extends SimpleIconBehaviors(DDD) { +class SimpleIcon extends SimpleIconBehaviors(SimpleColors) { /** * This is a convention, not the standard */ diff --git a/elements/simple-img/.gitignore b/elements/simple-img/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-img/.gitignore +++ b/elements/simple-img/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-img/demo/index.html b/elements/simple-img/demo/index.html index a600262fc1..478d04c3ff 100644 --- a/elements/simple-img/demo/index.html +++ b/elements/simple-img/demo/index.html @@ -4,7 +4,6 @@ SimpleImg: simple-img Demo - - +
      diff --git a/elements/simple-img/index.html b/elements/simple-img/index.html index be102ddec8..7244004539 100644 --- a/elements/simple-img/index.html +++ b/elements/simple-img/index.html @@ -4,10 +4,10 @@ simple-img documentation - - + + - + diff --git a/elements/simple-img/package.json b/elements/simple-img/package.json index 1697a81893..d57fe5c22a 100644 --- a/elements/simple-img/package.json +++ b/elements/simple-img/package.json @@ -50,10 +50,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-login/.gitignore b/elements/simple-login/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-login/.gitignore +++ b/elements/simple-login/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-login/custom-elements.json b/elements/simple-login/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/simple-login/demo/index.html b/elements/simple-login/demo/index.html index 8441a2395c..7bfbc6edd1 100644 --- a/elements/simple-login/demo/index.html +++ b/elements/simple-login/demo/index.html @@ -10,7 +10,7 @@ - + + - + diff --git a/elements/simple-login/lib/simple-camera-snap.js b/elements/simple-login/lib/simple-camera-snap.js index 0372cea88a..9ef269814e 100644 --- a/elements/simple-login/lib/simple-camera-snap.js +++ b/elements/simple-login/lib/simple-camera-snap.js @@ -21,7 +21,6 @@ class SimpleCameraSnap extends HTMLElement { new URL("../locales/simple-login.es.json", import.meta.url).href + "/../", updateCallback: "render", - }, }), ); diff --git a/elements/simple-login/lib/simple-login-camera.js b/elements/simple-login/lib/simple-login-camera.js index 6158b1e0ee..ed7bc88bdc 100644 --- a/elements/simple-login/lib/simple-login-camera.js +++ b/elements/simple-login/lib/simple-login-camera.js @@ -31,7 +31,6 @@ class SimpleLoginCamera extends HTMLElement { new URL("../locales/simple-login.es.json", import.meta.url).href + "/../", updateCallback: "render", - }, }), ); diff --git a/elements/simple-login/package.json b/elements/simple-login/package.json old mode 100755 new mode 100644 index 6d42232bf0..43562631ca --- a/elements/simple-login/package.json +++ b/elements/simple-login/package.json @@ -47,18 +47,14 @@ "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/simple-progress": "^11.0.0", "@haxtheweb/simple-tooltip": "^11.0.0", - "@polymer/paper-progress": "3.0.1", - "lit": "3.3.0", + "lit": "3.3.1", "msr": "^1.3.4" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-modal/.gitignore b/elements/simple-modal/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-modal/.gitignore +++ b/elements/simple-modal/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-modal/custom-elements.json b/elements/simple-modal/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/simple-modal/demo/css.html b/elements/simple-modal/demo/css.html index c6258ebc60..546321c3cd 100644 --- a/elements/simple-modal/demo/css.html +++ b/elements/simple-modal/demo/css.html @@ -9,7 +9,7 @@ diff --git a/elements/simple-modal/demo/details.html b/elements/simple-modal/demo/details.html index e7b6933cbb..eddd35239f 100644 --- a/elements/simple-modal/demo/details.html +++ b/elements/simple-modal/demo/details.html @@ -9,7 +9,7 @@ diff --git a/elements/simple-modal/demo/index.html b/elements/simple-modal/demo/index.html index 08bb70bde8..95dd59d4cc 100644 --- a/elements/simple-modal/demo/index.html +++ b/elements/simple-modal/demo/index.html @@ -9,12 +9,12 @@ - + diff --git a/elements/simple-modal/demo/template.html b/elements/simple-modal/demo/template.html index 71162e2104..fb088b230f 100644 --- a/elements/simple-modal/demo/template.html +++ b/elements/simple-modal/demo/template.html @@ -9,11 +9,11 @@ - +

      This is to illustrate the notion of some DIV being handed off to the modal but just a clone, not the real thing.

      diff --git a/elements/simple-modal/index.html b/elements/simple-modal/index.html index 1961a1c25a..8a34d25dbb 100755 --- a/elements/simple-modal/index.html +++ b/elements/simple-modal/index.html @@ -4,10 +4,10 @@ simple-modal documentation - - + + - + diff --git a/elements/simple-modal/package.json b/elements/simple-modal/package.json old mode 100755 new mode 100644 index 07f3f06822..02db45275f --- a/elements/simple-modal/package.json +++ b/elements/simple-modal/package.json @@ -43,7 +43,7 @@ "dependencies": { "@haxtheweb/d-d-d": "^11.0.5", "@haxtheweb/simple-icon": "^11.0.5", - "lit": "3.3.0", + "lit": "3.3.1", "web-dialog": "0.0.11" }, "devDependencies": { @@ -51,10 +51,7 @@ "@haxtheweb/deduping-fix": "^11.0.0", "@haxtheweb/simple-fields": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-modal/simple-modal.js b/elements/simple-modal/simple-modal.js index 7ae7d7ad82..42011e71d3 100644 --- a/elements/simple-modal/simple-modal.js +++ b/elements/simple-modal/simple-modal.js @@ -103,7 +103,10 @@ class SimpleModal extends LitElement { ); height: var(--simple-modal-titlebar-height, unset); line-height: var(--simple-modal-titlebar-line-height, unset); - color: var(--ddd-theme-default-nittanyNavy); + color: var( + --simple-modal-titlebar-color, + var(--ddd-theme-default-nittanyNavy) + ); font-size: var(--ddd-theme-h3-font-size); } @@ -122,7 +125,10 @@ class SimpleModal extends LitElement { min-width: unset; text-transform: none; background-color: transparent; - color: var(--ddd-theme-default-nittanyNavy); + color: var( + --simple-modal-titlebar-color, + var(--ddd-theme-default-nittanyNavy) + ); --simple-icon-width: var(--ddd-icon-sm); --simple-icon-height: var(--ddd-icon-sm); } @@ -145,7 +151,10 @@ class SimpleModal extends LitElement { var(--ddd-spacing-2) var(--ddd-spacing-4) var(--ddd-spacing-4) ); margin: 0; - color: var(--simple-modal-content-container-color, var(--ddd-theme-primary)); + color: var( + --simple-modal-content-container-color, + var(--ddd-theme-primary) + ); background-color: var( --simple-modal-content-container-background, var(--ddd-theme-default-white) @@ -477,6 +486,10 @@ class SimpleModal extends LitElement { * Close the modal and do some clean up */ close() { + // Restore body scrolling + document.body.style.overflow = ""; + document.documentElement.style.overflow = ""; + this.opened = false; if (globalThis.ShadyCSS && !globalThis.ShadyCSS.nativeShadow) { this.shadowRoot @@ -519,6 +532,9 @@ class SimpleModal extends LitElement { // Observer opened for changes _openedChanged(newValue) { if (typeof newValue !== typeof undefined && !newValue) { + // Restore body scrolling when modal closes + document.body.style.overflow = ""; + document.documentElement.style.overflow = ""; // wipe the slot of our modal this.title = ""; while (this.firstChild !== null) { @@ -539,6 +555,8 @@ class SimpleModal extends LitElement { }); this.dispatchEvent(evt); } else if (newValue) { + // Prevent body scrolling when modal opens + document.body.style.overflow = "hidden"; // p dialog backport; a nice, simple solution for close buttons let dismiss = this.querySelectorAll("[dialog-dismiss]"); dismiss.forEach((el) => { diff --git a/elements/simple-picker/.gitignore b/elements/simple-picker/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-picker/.gitignore +++ b/elements/simple-picker/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-picker/demo/index.html b/elements/simple-picker/demo/index.html index 394aed46b3..3fb54e073f 100644 --- a/elements/simple-picker/demo/index.html +++ b/elements/simple-picker/demo/index.html @@ -12,7 +12,7 @@ }; - + + - + diff --git a/elements/simple-picker/package.json b/elements/simple-picker/package.json old mode 100755 new mode 100644 index adb29fb844..6efd3f112e --- a/elements/simple-picker/package.json +++ b/elements/simple-picker/package.json @@ -43,16 +43,13 @@ "@haxtheweb/intersection-element": "^11.0.0", "@haxtheweb/simple-icon": "^11.0.5", "@lit-labs/virtualizer": "2.1.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-popover/.gitignore b/elements/simple-popover/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-popover/.gitignore +++ b/elements/simple-popover/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-popover/custom-elements.json b/elements/simple-popover/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/simple-popover/demo/index.html b/elements/simple-popover/demo/index.html index e56cfa59fa..f30f0144f4 100644 --- a/elements/simple-popover/demo/index.html +++ b/elements/simple-popover/demo/index.html @@ -9,7 +9,7 @@ - + + - + diff --git a/elements/simple-popover/lib/simple-popover-selection.js b/elements/simple-popover/lib/simple-popover-selection.js index 2c91871571..5d7049461b 100644 --- a/elements/simple-popover/lib/simple-popover-selection.js +++ b/elements/simple-popover/lib/simple-popover-selection.js @@ -90,10 +90,13 @@ class SimplePopoverSelection extends LitElement { } }, 0); } + // Use left-right orientation so popover appears on opposite side of menu + // If tray is on left, popover appears on right, and vice versa globalThis.SimplePopoverManager.requestAvailability().setPopover( this, this.querySelector('[slot="button"]'), state, + "lr", ); } /** diff --git a/elements/simple-popover/package.json b/elements/simple-popover/package.json old mode 100755 new mode 100644 index 3bd6c4f6b7..85ded15dd7 --- a/elements/simple-popover/package.json +++ b/elements/simple-popover/package.json @@ -47,16 +47,13 @@ "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/simple-toolbar": "^11.0.5", "@haxtheweb/simple-tooltip": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-progress/.gitignore b/elements/simple-progress/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-progress/.gitignore +++ b/elements/simple-progress/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-progress/demo/index.html b/elements/simple-progress/demo/index.html index 61246e454c..568ee9577a 100644 --- a/elements/simple-progress/demo/index.html +++ b/elements/simple-progress/demo/index.html @@ -4,15 +4,14 @@ SimpleProgress: simple-progress Demo - - +
      diff --git a/elements/simple-progress/index.html b/elements/simple-progress/index.html index 7191b2fcb4..52d009290f 100644 --- a/elements/simple-progress/index.html +++ b/elements/simple-progress/index.html @@ -4,10 +4,10 @@ simple-progress documentation - - + + - + diff --git a/elements/simple-progress/package.json b/elements/simple-progress/package.json index bdfc9bcffe..a571f3e2ed 100644 --- a/elements/simple-progress/package.json +++ b/elements/simple-progress/package.json @@ -46,10 +46,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-range-input/.gitignore b/elements/simple-range-input/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-range-input/.gitignore +++ b/elements/simple-range-input/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-range-input/demo/index.html b/elements/simple-range-input/demo/index.html index 324cdecc7b..6de4567613 100644 --- a/elements/simple-range-input/demo/index.html +++ b/elements/simple-range-input/demo/index.html @@ -4,15 +4,14 @@ SimpleRangeInput: simple-range-input Demo - - +
      diff --git a/elements/simple-range-input/index.html b/elements/simple-range-input/index.html index d3db87bd14..445c89a9e3 100644 --- a/elements/simple-range-input/index.html +++ b/elements/simple-range-input/index.html @@ -4,10 +4,10 @@ simple-range-input documentation - - + + - + diff --git a/elements/simple-range-input/package.json b/elements/simple-range-input/package.json index 1412378310..79bc327337 100644 --- a/elements/simple-range-input/package.json +++ b/elements/simple-range-input/package.json @@ -44,16 +44,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/simple-colors": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-search/.gitignore b/elements/simple-search/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-search/.gitignore +++ b/elements/simple-search/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-search/custom-elements.json b/elements/simple-search/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/simple-search/demo/index.html b/elements/simple-search/demo/index.html index 3cd23d2088..e85be6f814 100644 --- a/elements/simple-search/demo/index.html +++ b/elements/simple-search/demo/index.html @@ -7,7 +7,7 @@ + - + + - + diff --git a/elements/simple-toast/package.json b/elements/simple-toast/package.json old mode 100755 new mode 100644 index a37c28b9c2..49eddbf18e --- a/elements/simple-toast/package.json +++ b/elements/simple-toast/package.json @@ -44,16 +44,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/d-d-d": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-toolbar/.gitignore b/elements/simple-toolbar/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-toolbar/.gitignore +++ b/elements/simple-toolbar/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-toolbar/custom-elements.json b/elements/simple-toolbar/custom-elements.json index 661281552a..fd1900543f 100644 --- a/elements/simple-toolbar/custom-elements.json +++ b/elements/simple-toolbar/custom-elements.json @@ -622,7 +622,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "attribute": "shortcut-keys" }, @@ -677,7 +677,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "attribute": "more-shortcuts" } ], @@ -745,7 +745,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "fieldName": "moreShortcuts" }, { @@ -753,7 +753,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "fieldName": "shortcutKeys" }, @@ -905,7 +905,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "fieldName": "moreShortcuts", "inheritedFrom": { "name": "SimpleToolbarBehaviors", @@ -917,7 +917,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "fieldName": "shortcutKeys", "inheritedFrom": { @@ -1827,7 +1827,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "attribute": "shortcut-keys", "inheritedFrom": { @@ -1902,7 +1902,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "attribute": "more-shortcuts", "inheritedFrom": { "name": "SimpleToolbarBehaviors", @@ -2837,7 +2837,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "attribute": "shortcut-keys", "inheritedFrom": { @@ -2912,7 +2912,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "attribute": "more-shortcuts", "inheritedFrom": { "name": "SimpleToolbarBehaviors", @@ -3021,7 +3021,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "fieldName": "moreShortcuts", "inheritedFrom": { "name": "SimpleToolbarBehaviors", @@ -3033,7 +3033,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "fieldName": "shortcutKeys", "inheritedFrom": { @@ -3260,7 +3260,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "fieldName": "moreShortcuts", "inheritedFrom": { "name": "SimpleToolbarBehaviors", @@ -3272,7 +3272,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "fieldName": "shortcutKeys", "inheritedFrom": { @@ -4244,7 +4244,7 @@ "type": { "text": "object" }, - "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-sperated list of keyboard shortcuts for editor\nto fire this button", "default": "{}", "attribute": "shortcut-keys", "inheritedFrom": { @@ -4319,7 +4319,7 @@ "type": { "text": "object" }, - "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button, see iron-a11y-keys for more info.", + "description": "Optional space-separated list of keyboard shortcuts for editor\nto fire this button", "attribute": "more-shortcuts", "inheritedFrom": { "name": "SimpleToolbarBehaviors", @@ -6818,6 +6818,12 @@ "name": "menuItem", "description": "gets item with role=\"menuitem\"", "readonly": true + }, + { + "kind": "field", + "name": "focusableElement", + "description": "gets focusable element within menuitem", + "readonly": true } ], "mixins": [ diff --git a/elements/simple-toolbar/demo/buttons.html b/elements/simple-toolbar/demo/buttons.html index 594f41e765..0f4d2d50f2 100644 --- a/elements/simple-toolbar/demo/buttons.html +++ b/elements/simple-toolbar/demo/buttons.html @@ -4,17 +4,16 @@ SimpleToolbar: simple-toolbar Demo - - +
      diff --git a/elements/simple-toolbar/demo/grid.html b/elements/simple-toolbar/demo/grid.html index c2a60dda85..fd6f48a512 100644 --- a/elements/simple-toolbar/demo/grid.html +++ b/elements/simple-toolbar/demo/grid.html @@ -4,15 +4,14 @@ SimpleToolbar: simple-toolbar Demo - - +
      diff --git a/elements/simple-toolbar/demo/index.html b/elements/simple-toolbar/demo/index.html index 756357e445..4c558f0f44 100644 --- a/elements/simple-toolbar/demo/index.html +++ b/elements/simple-toolbar/demo/index.html @@ -4,18 +4,17 @@ SimpleToolbar: simple-toolbar Demo - - +
      diff --git a/elements/simple-toolbar/demo/menu.html b/elements/simple-toolbar/demo/menu.html index 2fa45a958f..69f5bdc367 100644 --- a/elements/simple-toolbar/demo/menu.html +++ b/elements/simple-toolbar/demo/menu.html @@ -4,18 +4,17 @@ SimpleToolbar: simple-toolbar Demo - - +
      diff --git a/elements/simple-toolbar/index.html b/elements/simple-toolbar/index.html index f5a9361b3c..ba71aa8557 100644 --- a/elements/simple-toolbar/index.html +++ b/elements/simple-toolbar/index.html @@ -4,10 +4,10 @@ simple-toolbar documentation - - + + - + diff --git a/elements/simple-toolbar/lib/simple-toolbar-button.js b/elements/simple-toolbar/lib/simple-toolbar-button.js index 5c5571f076..f9326926d7 100644 --- a/elements/simple-toolbar/lib/simple-toolbar-button.js +++ b/elements/simple-toolbar/lib/simple-toolbar-button.js @@ -201,7 +201,7 @@ const SimpleToolbarButtonBehaviors = function (SuperClass) { this.toggles = false; this.radio = false; this.shortcutKeys = ""; - this.isCurrentItem = true; + this.isCurrentItem = false; } /** * gets button element @@ -538,7 +538,11 @@ const SimpleToolbarButtonBehaviors = function (SuperClass) { @focus="${this._handleFocus}" part="button" role="radio" - tabindex="${this.isCurrentItem ? 0 : -1}" + tabindex="${this.role === "menuitem" + ? 0 + : this.isCurrentItem + ? 0 + : -1}" > ${this.buttonInnerTemplate} @@ -559,7 +563,11 @@ const SimpleToolbarButtonBehaviors = function (SuperClass) { @blur="${this._handleBlur}" @focus="${this._handleFocus}" part="button" - tabindex="${this.isCurrentItem ? 0 : -1}" + tabindex="${this.role === "menuitem" + ? 0 + : this.isCurrentItem + ? 0 + : -1}" > ${this.buttonInnerTemplate} @@ -578,7 +586,11 @@ const SimpleToolbarButtonBehaviors = function (SuperClass) { @blur="${this._handleBlur}" @focus="${this._handleFocus}" part="button" - tabindex="${this.isCurrentItem ? 0 : -1}" + tabindex="${this.role === "menuitem" + ? 0 + : this.isCurrentItem + ? 0 + : -1}" > ${this.buttonInnerTemplate} diff --git a/elements/simple-toolbar/lib/simple-toolbar-menu-item.js b/elements/simple-toolbar/lib/simple-toolbar-menu-item.js index 4d960c4733..f40d1cd1df 100644 --- a/elements/simple-toolbar/lib/simple-toolbar-menu-item.js +++ b/elements/simple-toolbar/lib/simple-toolbar-menu-item.js @@ -45,6 +45,15 @@ class SimpleToolbarMenuItem extends A11yMenuButtonItemBehaviors(LitElement) { get menuItem() { return this.querySelector("[role=menuitem]") || super.menuItem; } + /** + * gets focusable element within menuitem + * + * @readonly + */ + get focusableElement() { + let item = this.menuItem; + return item && item.focusableElement ? item.focusableElement : item; + } } globalThis.customElements.define( SimpleToolbarMenuItem.tag, diff --git a/elements/simple-toolbar/lib/simple-toolbar-menu.js b/elements/simple-toolbar/lib/simple-toolbar-menu.js index e685527d3a..f6b620a87f 100644 --- a/elements/simple-toolbar/lib/simple-toolbar-menu.js +++ b/elements/simple-toolbar/lib/simple-toolbar-menu.js @@ -39,6 +39,7 @@ const SimpleToolbarMenuBehaviors = function (SuperClass) { --simple-icon-width: 18px; position: absolute; left: 2px; + color: var(--simple-toolbar-button-color, currentColor); } `, ]; @@ -115,9 +116,12 @@ const SimpleToolbarMenuBehaviors = function (SuperClass) { aria-controls="menu" ?disabled="${this.disabled}" aria-expanded="${this.expanded ? "true" : "false"}" + @click="${this._handleClick}" + @keydown="${this._handleKeydown}" + @focus="${this._handleFocus}" @blur="${this._handleBlur}" part="button" - tabindex="${this.isCurrentItem ? 1 : -1}" + tabindex="0" > ${this.buttonInnerTemplate} + item === relatedTarget || + item.contains(relatedTarget) || + (item.focusableElement && + (item.focusableElement === relatedTarget || + item.focusableElement.contains(relatedTarget))), + ); + if (isStayingInMenu) { + return; + } + } + if (!this.isCurrentItem) setTimeout(() => this.close(), 300); } /** diff --git a/elements/simple-toolbar/package.json b/elements/simple-toolbar/package.json index d55fc35f78..0fab625b54 100644 --- a/elements/simple-toolbar/package.json +++ b/elements/simple-toolbar/package.json @@ -49,15 +49,12 @@ "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/simple-tooltip": "^11.0.0", "@haxtheweb/utils": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "gulp-sourcemaps": "3.0.0", "wct-browser-legacy": "1.0.2" diff --git a/elements/simple-toolbar/simple-toolbar.js b/elements/simple-toolbar/simple-toolbar.js index 511d39022e..7ba087250e 100644 --- a/elements/simple-toolbar/simple-toolbar.js +++ b/elements/simple-toolbar/simple-toolbar.js @@ -95,12 +95,6 @@ const SimpleToolbarBehaviors = function (SuperClass) { var(--simple-toolbar-group-border-color, transparent) ); } - ::slotted(.group:not(:last-child)) { - border-right-width: var( - --simple-toolbar-group-border-width, - var(--simple-toolbar-border-width, 1px) - ); - } ::slotted(*:hover), ::slotted(*:focus-wthin) { z-index: var(--simple-toolbar-focus-z-index, 100); @@ -178,7 +172,7 @@ const SimpleToolbarBehaviors = function (SuperClass) { }, /** * Optional space-separated list of keyboard shortcuts for editor - * to fire this button, see iron-a11y-keys for more info. + * to fire this button */ moreShortcuts: { name: "moreShortcuts", @@ -187,7 +181,7 @@ const SimpleToolbarBehaviors = function (SuperClass) { }, /** * Optional space-sperated list of keyboard shortcuts for editor - * to fire this button, see iron-a11y-keys for more info. + * to fire this button */ shortcutKeys: { name: "shortcutKeys", @@ -662,65 +656,113 @@ const SimpleToolbarBehaviors = function (SuperClass) { let finished = false; let key = this._shortcutKeysMatch(e); if (key) return; - let c; + + // Allow simple-toolbar-menu to handle DOWN/SPACE/ENTER to expand its menu + if ( + this.currentItem && + this.currentItem.tagName === "SIMPLE-TOOLBAR-MENU" + ) { + if ( + [this.keyCode.DOWN, this.keyCode.SPACE, this.keyCode.ENTER].includes( + e.keyCode, + ) + ) { + // Let the menu handle these keys to expand + return; + } + } + + let c, startIndex; switch (e.keyCode) { case this.keyCode.RIGHT: + startIndex = this.getItemIndex(); c = this.nextItem || this.firstItem; - while (c && c.disabled) { - this.currentItem = c; + // Keep searching for next non-hidden, non-disabled item + while (c && (c.disabled || c.hidden)) { + this.setCurrentItem(c); c = this.nextItem || this.firstItem; + // Prevent infinite loop - if we've wrapped around + if (this.getItemIndex() === startIndex) { + c = null; + break; + } } - this.focusOn(this.nextItem || this.firstItem); + if (c) this.focusOn(c); finished = true; break; case this.keyCode.LEFT: + startIndex = this.getItemIndex(); c = this.previousItem || this.lastItem; - while (c && c.disabled) { - this.currentItem = c; + while (c && (c.disabled || c.hidden)) { + this.setCurrentItem(c); c = this.previousItem || this.lastItem; + if (this.getItemIndex() === startIndex) { + c = null; + break; + } } - this.focusOn(this.previousItem || this.lastItem); + if (c) this.focusOn(c); finished = true; break; case this.keyCode.HOME: c = this.firstItem; - while (c && c.disabled) { - this.currentItem = c; - c = this.firstItem; + startIndex = this.getItemIndex(c); + while (c && (c.disabled || c.hidden)) { + this.setCurrentItem(c); + c = this.nextItem || this.firstItem; + if (this.getItemIndex() === startIndex) { + c = null; + break; + } } - this.focusOn(this.firstItem); + if (c) this.focusOn(c); finished = true; break; case this.keyCode.END: c = this.lastItem; - while (c && c.disabled) { - this.currentItem = c; - c = this.lastItem; + startIndex = this.getItemIndex(c); + while (c && (c.disabled || c.hidden)) { + this.setCurrentItem(c); + c = this.previousItem || this.lastItem; + if (this.getItemIndex() === startIndex) { + c = null; + break; + } } - this.focusOn(this.lastItem); + if (c) this.focusOn(c); finished = true; break; case this.keyCode.UP: + startIndex = this.getItemIndex(); c = this.previousItem || this.lastItem; - while (c && c.disabled) { - this.currentItem = c; + while (c && (c.disabled || c.hidden)) { + this.setCurrentItem(c); c = this.previousItem || this.lastItem; + if (this.getItemIndex() === startIndex) { + c = null; + break; + } } - this.focusOn(this.previousItem || this.lastItem); + if (c) this.focusOn(c); finished = true; break; case this.keyCode.DOWN: + startIndex = this.getItemIndex(); c = this.nextItem || this.firstItem; - while (c && c.disabled) { - this.currentItem = c; + while (c && (c.disabled || c.hidden)) { + this.setCurrentItem(c); c = this.nextItem || this.firstItem; + if (this.getItemIndex() === startIndex) { + c = null; + break; + } } - this.focusOn(this.nextItem || this.firstItem); + if (c) this.focusOn(c); finished = true; break; diff --git a/elements/simple-tooltip/.gitignore b/elements/simple-tooltip/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-tooltip/.gitignore +++ b/elements/simple-tooltip/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-tooltip/custom-elements.json b/elements/simple-tooltip/custom-elements.json old mode 100755 new mode 100644 index 4372a0bf82..aa919bfa2a --- a/elements/simple-tooltip/custom-elements.json +++ b/elements/simple-tooltip/custom-elements.json @@ -8,7 +8,7 @@ "declarations": [ { "kind": "class", - "description": "`simple-tooltip`\n`a simple tooltip forked from paper-tooltip with the same api minus apply removal`\n ### Styling\n The following custom properties and mixins are available for styling:\n\n Custom property | Description | Default\n ----------------|-------------|----------\n `--simple-tooltip-background` | The background color of the tooltip | `#616161`\n `--simple-tooltip-opacity` | The opacity of the tooltip | `0.9`\n `--simple-tooltip-text-color` | The text color of the tooltip | `white`\n `--simple-tooltip-delay-in` | Delay before tooltip starts to fade in | `500`\n `--simple-tooltip-delay-out` | Delay before tooltip starts to fade out | `0`\n `--simple-tooltip-duration-in` | Timing for animation when showing tooltip | `500`\n `--simple-tooltip-duration-out` | Timing for animation when hiding tooltip | `0`\n `--simple-tooltip-padding` | padding on the wrapper for the tip | `8px`\n `--simple-tooltip-margin` | margin on the wrapper for the tip | `0px`", + "description": "`simple-tooltip`\n`a simple tooltip forked with the same api minus apply removal`\n ### Styling\n The following custom properties and mixins are available for styling:\n\n Custom property | Description | Default\n ----------------|-------------|----------\n `--simple-tooltip-background` | The background color of the tooltip | `#616161`\n `--simple-tooltip-opacity` | The opacity of the tooltip | `0.9`\n `--simple-tooltip-text-color` | The text color of the tooltip | `white`\n `--simple-tooltip-delay-in` | Delay before tooltip starts to fade in | `500`\n `--simple-tooltip-delay-out` | Delay before tooltip starts to fade out | `0`\n `--simple-tooltip-duration-in` | Timing for animation when showing tooltip | `500`\n `--simple-tooltip-duration-out` | Timing for animation when hiding tooltip | `0`\n `--simple-tooltip-padding` | padding on the wrapper for the tip | `8px`\n `--simple-tooltip-margin` | margin on the wrapper for the tip | `0px`", "name": "SimpleTooltip", "members": [ { diff --git a/elements/simple-tooltip/index.html b/elements/simple-tooltip/index.html index c2d5eb508d..bdba6d2137 100755 --- a/elements/simple-tooltip/index.html +++ b/elements/simple-tooltip/index.html @@ -4,10 +4,10 @@ simple-tooltip documentation - - + + - + diff --git a/elements/simple-tooltip/package.json b/elements/simple-tooltip/package.json index d858b3f82c..bdb23b99e9 100755 --- a/elements/simple-tooltip/package.json +++ b/elements/simple-tooltip/package.json @@ -16,7 +16,7 @@ } }, "version": "11.0.0", - "description": "a simple tooltip forked from paper-tooltip with the same api", + "description": "a simple tooltip forked with the same api", "repository": { "type": "git", "url": "https://github.com/haxtheweb/webcomponents.git" @@ -40,7 +40,7 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", @@ -48,7 +48,6 @@ "@haxtheweb/simple-icon": "^11.0.5", "@open-wc/testing": "4.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/simple-tooltip/simple-tooltip.js b/elements/simple-tooltip/simple-tooltip.js index 6c7fa16b39..fcaae0a0f6 100644 --- a/elements/simple-tooltip/simple-tooltip.js +++ b/elements/simple-tooltip/simple-tooltip.js @@ -6,7 +6,7 @@ import { LitElement, html, css } from "lit"; /** * `simple-tooltip` - * `a simple tooltip forked from paper-tooltip with the same api minus apply removal` + * `a simple tooltip forked with the same api minus apply removal` ### Styling The following custom properties and mixins are available for styling: diff --git a/elements/simple-wc/.gitignore b/elements/simple-wc/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/simple-wc/.gitignore +++ b/elements/simple-wc/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/simple-wc/custom-elements.json b/elements/simple-wc/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/simple-wc/demo/simple-wc-demo.js b/elements/simple-wc/demo/simple-wc-demo.js index 79d1aa41ba..6a48c8b380 100644 --- a/elements/simple-wc/demo/simple-wc-demo.js +++ b/elements/simple-wc/demo/simple-wc-demo.js @@ -50,13 +50,11 @@ createSWC({ // HTML contents, el is the element itself and html is the processing function html: (el, html) => { return html` - - - + `; }, // CSS styles, el is the element itself and css is the processing function diff --git a/elements/simple-wc/index.html b/elements/simple-wc/index.html index 4b8e83279b..a5a1db121c 100755 --- a/elements/simple-wc/index.html +++ b/elements/simple-wc/index.html @@ -4,10 +4,10 @@ simple-wc documentation - - + + - + diff --git a/elements/simple-wc/package.json b/elements/simple-wc/package.json index ba5b80af80..5a942d6084 100755 --- a/elements/simple-wc/package.json +++ b/elements/simple-wc/package.json @@ -40,14 +40,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/social-share-link/.gitignore b/elements/social-share-link/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/social-share-link/.gitignore +++ b/elements/social-share-link/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/social-share-link/custom-elements.json b/elements/social-share-link/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/social-share-link/demo/index.html b/elements/social-share-link/demo/index.html index fd8d1aa95c..0f7cd12eed 100644 --- a/elements/social-share-link/demo/index.html +++ b/elements/social-share-link/demo/index.html @@ -9,10 +9,10 @@ - +
      diff --git a/elements/social-share-link/index.html b/elements/social-share-link/index.html index c8b86486d4..67ee0c8896 100755 --- a/elements/social-share-link/index.html +++ b/elements/social-share-link/index.html @@ -4,10 +4,10 @@ social-share-link documentation - - + + - + diff --git a/elements/social-share-link/package.json b/elements/social-share-link/package.json old mode 100755 new mode 100644 index 1f5f62e08d..a3e579eec6 --- a/elements/social-share-link/package.json +++ b/elements/social-share-link/package.json @@ -43,16 +43,13 @@ "dependencies": { "@haxtheweb/hax-iconset": "^11.0.0", "@haxtheweb/simple-icon": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/sorting-question/.gitignore b/elements/sorting-question/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/sorting-question/.gitignore +++ b/elements/sorting-question/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/sorting-question/custom-elements.json b/elements/sorting-question/custom-elements.json deleted file mode 100644 index 1777b07663..0000000000 --- a/elements/sorting-question/custom-elements.json +++ /dev/null @@ -1,353 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "sorting-question.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "SortingQuestion", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "getOptions", - "parameters": [ - { - "name": "flag", - "default": "\"\"" - } - ] - }, - { - "kind": "method", - "name": "checkAnswerCallback" - }, - { - "kind": "method", - "name": "resetAnswer" - }, - { - "kind": "method", - "name": "renderInteraction" - }, - { - "kind": "method", - "name": "inactiveCase" - }, - { - "kind": "method", - "name": "renderDirections" - }, - { - "kind": "method", - "name": "renderFeedback" - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "description": "haxProperties integration via file reference", - "readonly": true - }, - { - "kind": "method", - "name": "haxinlineContextMenu", - "parameters": [ - { - "name": "ceMenu" - } - ], - "description": "add buttons when it is in context" - }, - { - "kind": "method", - "name": "haxClickInlineAdd", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "haxClickInlineRemove", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "field", - "name": "randomize", - "type": { - "text": "boolean" - }, - "default": "true" - }, - { - "kind": "field", - "name": "numberCorrect", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "0", - "attribute": "numberCorrect" - }, - { - "kind": "field", - "name": "quizName", - "type": { - "text": "string" - }, - "default": "\"default\"" - }, - { - "kind": "field", - "name": "question", - "type": { - "text": "string" - }, - "default": "\"Put the following in order\"" - }, - { - "kind": "field", - "name": "t", - "type": { - "text": "object" - }, - "default": "{ numCorrectLeft: \"You have\", numCorrectRight: \"correct\", checkAnswer: \"Check answer\", tryAgain: \"Try again\", }" - } - ], - "events": [ - { - "name": "user-engagement", - "type": { - "text": "CustomEvent" - } - } - ], - "attributes": [ - { - "name": "numberCorrect", - "type": { - "text": "number" - }, - "default": "0", - "fieldName": "numberCorrect" - } - ], - "superclass": { - "name": "QuestionElement", - "package": "@haxtheweb/multiple-choice/lib/QuestionElement.js" - } - } - ], - "exports": [ - { - "kind": "js", - "name": "SortingQuestion", - "declaration": { - "name": "SortingQuestion", - "module": "sorting-question.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "SortingQuestion", - "module": "sorting-question.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/sorting-option.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "SortingOption", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "getCurrentPosition", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "dragStart", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "dragStartCallback", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "dragEnd" - }, - { - "kind": "method", - "name": "arrowSort", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "method", - "name": "arrowSortCallback", - "parameters": [ - { - "name": "target" - } - ] - }, - { - "kind": "field", - "name": "shadowRootOptions", - "type": { - "text": "object" - }, - "default": "{ ...LitElement.shadowRootOptions, delegatesFocus: true, }" - }, - { - "kind": "field", - "name": "dragging", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "dragging", - "reflects": true - }, - { - "kind": "field", - "name": "disabled", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "disabled", - "reflects": true - }, - { - "kind": "field", - "name": "correct", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "correct", - "reflects": true - }, - { - "kind": "field", - "name": "incorrect", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "incorrect", - "reflects": true - } - ], - "attributes": [ - { - "name": "disabled", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "disabled" - }, - { - "name": "dragging", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "dragging" - }, - { - "name": "correct", - "type": { - "text": "boolean" - }, - "fieldName": "correct" - }, - { - "name": "incorrect", - "type": { - "text": "boolean" - }, - "fieldName": "incorrect" - } - ], - "mixins": [ - { - "name": "DDDSuper", - "package": "@haxtheweb/d-d-d/d-d-d.js" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "js", - "name": "SortingOption", - "declaration": { - "name": "SortingOption", - "module": "lib/sorting-option.js" - } - }, - { - "kind": "custom-element-definition", - "declaration": { - "name": "SortingOption", - "module": "lib/sorting-option.js" - } - } - ] - } - ] -} diff --git a/elements/sorting-question/demo/index.html b/elements/sorting-question/demo/index.html index 3463f3f99f..725daf8ca0 100644 --- a/elements/sorting-question/demo/index.html +++ b/elements/sorting-question/demo/index.html @@ -4,7 +4,6 @@ SortingQuestion: sorting-question Demo - - + + - + diff --git a/elements/sorting-question/package.json b/elements/sorting-question/package.json index a31fd52eb1..1c9c5dc252 100644 --- a/elements/sorting-question/package.json +++ b/elements/sorting-question/package.json @@ -50,16 +50,13 @@ "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/simple-toast": "^11.0.5", "@haxtheweb/simple-toolbar": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/sorting-question/sorting-question.js b/elements/sorting-question/sorting-question.js index 5eb43112f4..c310d72fba 100644 --- a/elements/sorting-question/sorting-question.js +++ b/elements/sorting-question/sorting-question.js @@ -348,14 +348,20 @@ export class SortingQuestion extends QuestionElement {

      ${this.querySelector && this.querySelector('[slot="feedbackIncorrect"]') - ? html`` + ? html`` : ``}` : ``} ${this.showAnswer && this.numberCorrect === this.answers.length ? html`

      ${this.correctText}

      ${this.querySelector && this.querySelector('[slot="feedbackCorrect"]') - ? html`` + ? html`` : ``}` : ``} ${this.querySelector && diff --git a/elements/spacebook-theme/spacebook-theme.js b/elements/spacebook-theme/spacebook-theme.js index 36006c15fc..0555ca282e 100644 --- a/elements/spacebook-theme/spacebook-theme.js +++ b/elements/spacebook-theme/spacebook-theme.js @@ -113,13 +113,13 @@ export class SpacebookTheme extends HAXCMSThemeParts(DDDSuper(HAXCMSLitElementTh body.dark-mode { background-color: var(--spacebook-theme-bg-gray-900); - color: var(--spacebook-theme-text-gray-400); + color: var(--spacebook-theme-text-gray-300); } @media (prefers-color-scheme: dark) { body:not(.light-mode) { background-color: var(--spacebook-theme-bg-gray-900); - color: var(--spacebook-theme-text-gray-400); + color: var(--spacebook-theme-text-gray-300); } } `, @@ -210,8 +210,8 @@ export class SpacebookTheme extends HAXCMSThemeParts(DDDSuper(HAXCMSLitElementTh color: var(--spacebook-theme-text-gray-800); } - :host([dark-mode]) .site-title-link { - color: var(--spacebook-theme-text-gray-500); +:host([dark-mode]) .site-title-link { + color: var(--spacebook-theme-text-gray-300); } .site-title { @@ -234,7 +234,7 @@ export class SpacebookTheme extends HAXCMSThemeParts(DDDSuper(HAXCMSLitElementTh } :host([dark-mode]) .site-subtitle { - color: var(--spacebook-theme-text-gray-600); + color: var(--spacebook-theme-text-gray-300); } /* Header controls group */ @@ -286,6 +286,14 @@ export class SpacebookTheme extends HAXCMSThemeParts(DDDSuper(HAXCMSLitElementTh color: var(--spacebook-theme-text-gray-600); } + :host([dark-mode]) .mobile-menu-btn { + color: var(--spacebook-theme-text-gray-300); + } + + :host([dark-mode]) .mobile-menu-btn:hover { + color: var(--spacebook-theme-text-gray-300); + } + /* Dark mode toggle */ .dark-mode-toggle { padding: 0.5rem; @@ -574,6 +582,14 @@ export class SpacebookTheme extends HAXCMSThemeParts(DDDSuper(HAXCMSLitElementTh background-color: var(--spacebook-theme-bg-gray-200); } + :host([dark-mode]) .page-meta-item { + color: var(--spacebook-theme-text-gray-300); + } + + :host([dark-mode]) .page-meta-item:hover { + background-color: var(--spacebook-theme-bg-gray-800); + } + .page-meta-item a { color: inherit; text-decoration: none; diff --git a/elements/spotify-embed/.gitignore b/elements/spotify-embed/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/spotify-embed/.gitignore +++ b/elements/spotify-embed/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/spotify-embed/custom-elements.json b/elements/spotify-embed/custom-elements.json deleted file mode 100644 index 149f2a3f70..0000000000 --- a/elements/spotify-embed/custom-elements.json +++ /dev/null @@ -1,183 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "spotify-embed.js", - "declarations": [ - { - "kind": "class", - "description": "`spotify-embed`\n`embed spotify playlists`", - "name": "SpotifyEmbed", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "Convention we use", - "readonly": true - }, - { - "kind": "method", - "name": "haxHooks" - }, - { - "kind": "method", - "name": "haxpreProcessNodeToContent", - "parameters": [ - { - "name": "node" - } - ] - }, - { - "kind": "method", - "name": "haxactiveElementChanged", - "parameters": [ - { - "name": "el" - }, - { - "name": "val" - } - ] - }, - { - "kind": "method", - "name": "haxeditModeChanged", - "parameters": [ - { - "name": "val" - } - ] - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "description": "haxProperties integration via file reference", - "readonly": true - }, - { - "kind": "field", - "name": "source", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "source" - }, - { - "kind": "field", - "name": "theme", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "theme" - }, - { - "kind": "field", - "name": "size", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"normal\"", - "attribute": "size" - }, - { - "kind": "field", - "name": "playlistid", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null" - }, - { - "kind": "field", - "name": "type", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null" - }, - { - "kind": "field", - "name": "editing", - "privacy": "public", - "type": { - "text": "boolean" - }, - "default": "false", - "attribute": "editing", - "reflects": true - } - ], - "attributes": [ - { - "name": "source", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "source" - }, - { - "name": "theme", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "theme" - }, - { - "name": "size", - "type": { - "text": "string" - }, - "default": "\"normal\"", - "fieldName": "size" - }, - { - "name": "editing", - "type": { - "text": "boolean" - }, - "default": "false", - "fieldName": "editing" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "spotify-embed", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "SpotifyEmbed", - "module": "spotify-embed.js" - } - }, - { - "kind": "js", - "name": "SpotifyEmbed", - "declaration": { - "name": "SpotifyEmbed", - "module": "spotify-embed.js" - } - } - ] - } - ] -} diff --git a/elements/spotify-embed/demo/index.html b/elements/spotify-embed/demo/index.html index 4c9f42019e..eb4cb2c3e5 100644 --- a/elements/spotify-embed/demo/index.html +++ b/elements/spotify-embed/demo/index.html @@ -4,14 +4,13 @@ SpotifyEmbed: spotify-embed Demo - - +
      diff --git a/elements/spotify-embed/index.html b/elements/spotify-embed/index.html index dfed3c2aa4..2b1371b0b3 100644 --- a/elements/spotify-embed/index.html +++ b/elements/spotify-embed/index.html @@ -4,10 +4,10 @@ spotify-embed documentation - - + + - + diff --git a/elements/spotify-embed/package.json b/elements/spotify-embed/package.json index 0d0ab98d64..c21dc88e04 100644 --- a/elements/spotify-embed/package.json +++ b/elements/spotify-embed/package.json @@ -43,16 +43,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/star-rating/.gitignore b/elements/star-rating/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/star-rating/.gitignore +++ b/elements/star-rating/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/star-rating/custom-elements.json b/elements/star-rating/custom-elements.json deleted file mode 100644 index cf9da36341..0000000000 --- a/elements/star-rating/custom-elements.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "star-rating.js", - "declarations": [ - { - "kind": "class", - "description": "`star-rating`\n`Rating display widget or button to do rating`", - "name": "StarRating", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "method", - "name": "renderStar", - "parameters": [ - { - "name": "amount" - }, - { - "name": "interactive" - } - ] - }, - { - "kind": "method", - "name": "interactiveEvent", - "parameters": [ - { - "name": "e" - } - ] - }, - { - "kind": "field", - "name": "_calPercent", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "0", - "attribute": "_calPercent" - }, - { - "kind": "field", - "name": "numStars", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "5", - "attribute": "num-stars" - }, - { - "kind": "field", - "name": "score", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "10", - "attribute": "score" - }, - { - "kind": "field", - "name": "possible", - "privacy": "public", - "type": { - "text": "number" - }, - "default": "100", - "attribute": "possible" - }, - { - "kind": "field", - "name": "dark", - "type": { - "text": "boolean" - }, - "default": "true" - }, - { - "kind": "field", - "name": "contrast", - "type": { - "text": "number" - }, - "default": "0" - }, - { - "kind": "field", - "name": "accentColor", - "type": { - "text": "string" - }, - "default": "\"yellow\"" - }, - { - "kind": "field", - "name": "interactive", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "interactive", - "reflects": true - } - ], - "events": [ - { - "name": "star-rating-click", - "type": { - "text": "CustomEvent" - } - } - ], - "attributes": [ - { - "name": "score", - "type": { - "text": "number" - }, - "default": "10", - "fieldName": "score" - }, - { - "name": "possible", - "type": { - "text": "number" - }, - "default": "100", - "fieldName": "possible" - }, - { - "name": "interactive", - "type": { - "text": "boolean" - }, - "fieldName": "interactive" - }, - { - "name": "num-stars", - "type": { - "text": "number" - }, - "default": "5", - "fieldName": "numStars" - }, - { - "name": "_calPercent", - "type": { - "text": "number" - }, - "default": "0", - "fieldName": "_calPercent" - } - ], - "superclass": { - "name": "SimpleColors", - "package": "@haxtheweb/simple-colors/simple-colors.js" - }, - "tagName": "star-rating", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "StarRating", - "module": "star-rating.js" - } - }, - { - "kind": "js", - "name": "StarRating", - "declaration": { - "name": "StarRating", - "module": "star-rating.js" - } - } - ] - } - ] -} diff --git a/elements/star-rating/demo/index.html b/elements/star-rating/demo/index.html index 9497639c69..862c02c4d7 100644 --- a/elements/star-rating/demo/index.html +++ b/elements/star-rating/demo/index.html @@ -4,15 +4,14 @@ StarRating: star-rating Demo - - +
      diff --git a/elements/star-rating/index.html b/elements/star-rating/index.html index ae6360b03a..83860c498a 100644 --- a/elements/star-rating/index.html +++ b/elements/star-rating/index.html @@ -4,10 +4,10 @@ star-rating documentation - - + + - + diff --git a/elements/star-rating/package.json b/elements/star-rating/package.json index fa3b89bd7e..43c7f7644c 100644 --- a/elements/star-rating/package.json +++ b/elements/star-rating/package.json @@ -46,16 +46,13 @@ "dependencies": { "@haxtheweb/simple-colors": "^11.0.5", "@haxtheweb/simple-icon": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/stop-note/.gitignore b/elements/stop-note/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/stop-note/.gitignore +++ b/elements/stop-note/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/stop-note/custom-elements.json b/elements/stop-note/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/stop-note/demo/index.html b/elements/stop-note/demo/index.html index 74fcfc7ff5..2d8ecd5097 100644 --- a/elements/stop-note/demo/index.html +++ b/elements/stop-note/demo/index.html @@ -33,7 +33,7 @@ } - + + - + diff --git a/elements/super-daemon/lib/super-daemon-row.js b/elements/super-daemon/lib/super-daemon-row.js index 2a69ae4074..3b079cb1e1 100644 --- a/elements/super-daemon/lib/super-daemon-row.js +++ b/elements/super-daemon/lib/super-daemon-row.js @@ -15,7 +15,7 @@ export class SuperDaemonRow extends I18NMixin(SimpleColors) { this.registerLocalization({ context: this, namespace: "super-daemon", - basePath: import.meta.url, + basePath: import.meta.url + "/../../", }); this.title = null; this.path = null; @@ -197,10 +197,10 @@ export class SuperDaemonRow extends I18NMixin(SimpleColors) { max-width: unset; } .more { - --simple-icon-width: var(--ddd-icon-sm); - --simple-icon-height: var(--ddd-icon-sm); - width: var(--ddd-icon-sm); - height: var(--ddd-icon-sm); + --simple-icon-width: var(--ddd-icon-4xs); + --simple-icon-height: var(--ddd-icon-4xs); + width: var(--ddd-icon-4xs); + height: var(--ddd-icon-4xs); display: block; } summary { diff --git a/elements/super-daemon/lib/super-daemon-search.js b/elements/super-daemon/lib/super-daemon-search.js index 92d32a67b2..2e1d7e0d9a 100644 --- a/elements/super-daemon/lib/super-daemon-search.js +++ b/elements/super-daemon/lib/super-daemon-search.js @@ -44,10 +44,10 @@ export class SuperDaemonSearch extends I18NMixin(SimpleColors) { this.registerLocalization({ context: this, namespace: "super-daemon", - basePath: import.meta.url, + basePath: import.meta.url + "/../../", }); // Initialize with fallback values to prevent undefined during initial render - this.possibleActions = ["🔮 Insert blocks", "🕵 Find media 📺"]; + this.possibleActions = ["🪄 Do anything..", "🔍 Type to search..."]; } static get properties() { return { @@ -560,6 +560,7 @@ export class SuperDaemonSearch extends I18NMixin(SimpleColors) { getActiveTitle(context) { switch (context) { + case "CMS": case "/": return this.t.slashCommandsActive || "Slash commands active"; case ">": @@ -570,6 +571,7 @@ export class SuperDaemonSearch extends I18NMixin(SimpleColors) { getActiveIcon(context) { switch (context) { + case "CMS": case "/": return "hax:wand"; case ">": diff --git a/elements/super-daemon/lib/super-daemon-toast.js b/elements/super-daemon/lib/super-daemon-toast.js index bd5943748b..b8379ca7a6 100644 --- a/elements/super-daemon/lib/super-daemon-toast.js +++ b/elements/super-daemon/lib/super-daemon-toast.js @@ -27,7 +27,7 @@ export class SuperDaemonToast extends I18NMixin(SimpleToastEl) { this.registerLocalization({ context: this, namespace: "super-daemon", - basePath: import.meta.url, + basePath: import.meta.url + "/../../", }); this.awaitingMerlinInput = false; this.windowControllers = new AbortController(); @@ -110,7 +110,6 @@ export class SuperDaemonToast extends I18NMixin(SimpleToastEl) { border: var(--simple-toast-border); z-index: var(--simple-toast-z-index, 100000000); font-size: var(--simple-toast-font-size, 18px); - font-family: "Press Start 2P", sans-serif; font-weight: bold; text-align: center; vertical-align: middle; diff --git a/elements/super-daemon/lib/super-daemon-ui.js b/elements/super-daemon/lib/super-daemon-ui.js index b6705b6b3d..7fbaea6f28 100644 --- a/elements/super-daemon/lib/super-daemon-ui.js +++ b/elements/super-daemon/lib/super-daemon-ui.js @@ -34,7 +34,7 @@ export class SuperDaemonUI extends SimpleFilterMixin(I18NMixin(SimpleColors)) { this.registerLocalization({ context: this, namespace: "super-daemon", - basePath: import.meta.url, + basePath: import.meta.url + "/../../", }); this.opened = false; this.items = []; @@ -254,16 +254,13 @@ export class SuperDaemonUI extends SimpleFilterMixin(I18NMixin(SimpleColors)) { padding: var(--ddd-spacing-1); } super-daemon-row::part(action) { - font-size: var(--ddd-icon-xxs); - line-height: var(--ddd-icon-xxs); - height: var(--ddd-icon-xxs); max-width: unset; } super-daemon-row::part(tags) { display: none; } super-daemon-row::part(path) { - font-size: 12px; + font-size: 10px; } } :host([mini]) { @@ -392,8 +389,10 @@ export class SuperDaemonUI extends SimpleFilterMixin(I18NMixin(SimpleColors)) { this.shadowRoot.querySelector("super-daemon-search").selectInput(); } - setupProgram() { - this.programSearch = ""; + setupProgram(initialProgramSearch = "") { + // Set programSearch from the passed parameter if provided + // This avoids timing issues with property propagation from parent to child + this.programSearch = initialProgramSearch; this.focusInput(); this.selectInput(); // reset to top of results @@ -515,12 +514,30 @@ export class SuperDaemonUI extends SimpleFilterMixin(I18NMixin(SimpleColors)) { // feed results to the program as opposed to the global context based on program running inputfilterChanged(e) { + const value = + e.target && typeof e.target.value === "string" ? e.target.value : ""; + if (this.programName) { - // don't set like if we're in a program - this.programSearch = e.target.value; + // don't set like if we're in a program; the active program is + // responsible for filtering its own results based on input, and + // SimpleFilterMixin should see all programResults unfiltered. + this.programSearch = value; } else { - this.like = e.target.value; + this.like = value; } + + // Bubble a normalized value-changed event so the top-level super-daemon + // instance always has the live input text (used for create-page titles + // and other programs that depend on the raw input). + this.dispatchEvent( + new CustomEvent("value-changed", { + bubbles: true, + composed: true, + detail: { + value: value, + }, + }), + ); } listeningForInputChanged(e) { diff --git a/elements/super-daemon/package.json b/elements/super-daemon/package.json index 01b2e62313..293c583f2e 100644 --- a/elements/super-daemon/package.json +++ b/elements/super-daemon/package.json @@ -52,7 +52,7 @@ "@haxtheweb/simple-icon": "^11.0.5", "@haxtheweb/user-scaffold": "^11.0.0", "@lit-labs/virtualizer": "2.1.0", - "lit": "3.3.0", + "lit": "3.3.1", "mobx": "6.13.7", "web-dialog": "0.0.11" }, @@ -60,10 +60,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/super-daemon/super-daemon.js b/elements/super-daemon/super-daemon.js index 528dffef84..1cc3ca9751 100644 --- a/elements/super-daemon/super-daemon.js +++ b/elements/super-daemon/super-daemon.js @@ -101,6 +101,23 @@ class SuperDaemon extends I18NMixin(SimpleColors) { programModeActivated: "program mode activated", developerModeActivated: "developer mode activated", }; + // Konami Code detection + this.konamiSequence = [ + "ArrowUp", + "ArrowUp", + "ArrowDown", + "ArrowDown", + "ArrowLeft", + "ArrowRight", + "ArrowLeft", + "ArrowRight", + "KeyB", + "KeyA", + ]; + this.konamiProgress = []; + this.konamiTimeout = null; + // Expose reset method globally for debugging + this.resetKonamiCode = this.resetKonamiCode.bind(this); this.registerLocalization({ context: this, namespace: "super-daemon", @@ -139,6 +156,7 @@ class SuperDaemon extends I18NMixin(SimpleColors) { this.programName = null; this.programPlaceholder = null; this.commandContext = "*"; + this._programDebounceTimeout = null; const isSafari = globalThis.safari !== undefined; if (isSafari) { this.key1 = "Meta"; @@ -196,6 +214,8 @@ class SuperDaemon extends I18NMixin(SimpleColors) { disconnectedCallback() { this.windowControllers.abort(); this.windowControllers2.abort(); + // Clear any pending program execution timeouts + clearTimeout(this._programDebounceTimeout); super.disconnectedCallback(); } // waving the magic wand is a common feedback loop combination @@ -273,11 +293,16 @@ class SuperDaemon extends I18NMixin(SimpleColors) { // used to force a search prepopulation if (like != null) { this.like = like; + // If we're setting 'like' and programSearch is empty, use 'like' as the initial programSearch + // This ensures the program executes with the pre-filled value on first run + if (!this.programSearch && like) { + this.programSearch = like; + } } // ensure we have a program as this could be used for resetting program state if (this._programToRun) { setTimeout(() => { - this.shadowRoot.querySelector("super-daemon-ui").setupProgram(); + this.shadowRoot.querySelector("super-daemon-ui").setupProgram(this.programSearch); setTimeout(async () => { try { this.loading = true; @@ -349,7 +374,15 @@ class SuperDaemon extends I18NMixin(SimpleColors) { handleProgramEnter(e) { if (e.detail && e.detail.programName && e.detail.input) { const programName = e.detail.programName; - const input = e.detail.input; + let input = e.detail.input; + + // Prefer the live Merlin input value at the moment Enter is pressed + const SuperDaemonInstance = + globalThis.SuperDaemonManager && + globalThis.SuperDaemonManager.requestAvailability(); + if (SuperDaemonInstance && SuperDaemonInstance.value) { + input = SuperDaemonInstance.value; + } // Special handling for the create-page program if (programName === "create-page") { @@ -467,6 +500,9 @@ class SuperDaemon extends I18NMixin(SimpleColors) { } keyHandler(e) { + // Check for Konami Code sequence first (global detection) + this.checkKonamiCode(e); + // modifier required to activate if (this.allowedCallback()) { // open and close events @@ -577,7 +613,6 @@ class SuperDaemon extends I18NMixin(SimpleColors) { absolute-position-behavior super-daemon-ui[mini][wand] { margin: -18px 0 0 0; padding: 0; - width: 30vw; /* Use full expanded width in wand mode */ } absolute-position-behavior super-daemon-ui { width: var(--super-daemon-width, 300px); @@ -637,6 +672,8 @@ class SuperDaemon extends I18NMixin(SimpleColors) { this.programResults = []; this.programName = null; this.commandContext = "*"; + // Reset Konami Code sequence when closing + this.resetKonamiCode(); // important we stop listening when the UI goes away this.setListeningStatus(false); // hide the toast if it's up.. unless in santa mode.. @@ -718,13 +755,123 @@ class SuperDaemon extends I18NMixin(SimpleColors) { return a.priority < b.priority ? -1 : a.priority > b.priority ? 1 : 0; }); } + /** + * Reset Konami Code sequence + */ + resetKonamiCode() { + this.konamiProgress = []; + if (this.konamiTimeout) { + clearTimeout(this.konamiTimeout); + this.konamiTimeout = null; + } + } + + /** + * Check for Konami Code sequence + * @param {KeyboardEvent} e - The keyboard event + */ + checkKonamiCode(e) { + // Clear timeout if exists + if (this.konamiTimeout) { + clearTimeout(this.konamiTimeout); + } + + // Get the key code for the current key + let keyCode = e.code || e.key; + // Handle legacy key names for arrow keys + if (e.key === "ArrowUp") keyCode = "ArrowUp"; + else if (e.key === "ArrowDown") keyCode = "ArrowDown"; + else if (e.key === "ArrowLeft") keyCode = "ArrowLeft"; + else if (e.key === "ArrowRight") keyCode = "ArrowRight"; + else if (e.key === "b" || e.key === "B") keyCode = "KeyB"; + else if (e.key === "a" || e.key === "A") keyCode = "KeyA"; + + // Check if this key matches the next expected key in sequence + if (keyCode === this.konamiSequence[this.konamiProgress.length]) { + this.konamiProgress.push(keyCode); + + // Check if sequence is complete + if (this.konamiProgress.length === this.konamiSequence.length) { + this.konamiCodeEntered(); + this.konamiProgress = []; // Reset + return; + } + } else { + // Wrong key, reset sequence + this.konamiProgress = []; + // Check if this key could be the start of a new sequence + if (keyCode === this.konamiSequence[0]) { + this.konamiProgress.push(keyCode); + } + } + + // Set timeout to reset sequence after 3 seconds of inactivity + this.konamiTimeout = setTimeout(() => { + this.konamiProgress = []; + }, 3000); + } + + /** + * Handle successful Konami Code entry + */ + async konamiCodeEntered() { + // Clear the input if merlin is open to remove 'ba' characters + if (this.opened && this.shadowRoot.querySelector("super-daemon-ui")) { + this.value = ""; + const ui = this.shadowRoot.querySelector("super-daemon-ui"); + if (ui) { + ui.like = ""; + ui.programSearch = ""; + const search = ui.shadowRoot.querySelector("super-daemon-search"); + if (search) { + search.value = ""; + const inputField = search.shadowRoot.querySelector("#inputfilter"); + if (inputField) { + inputField.value = ""; + } + } + } + } + + // Play coin sound 3 times, then success sound + await this.playSound("coin2"); + await this.playSound("coin2"); + await this.playSound("coin2"); + await this.playSound("success"); + + // Dispatch event to let HAXcms know Konami Code was entered + globalThis.dispatchEvent( + new CustomEvent("super-daemon-konami-code", { + bubbles: true, + composed: true, + cancelable: true, + detail: { + timestamp: Date.now(), + source: "super-daemon", + }, + }), + ); + } + playSound(sound = "coin2") { return new Promise((resolve) => { - let playSound = ["coin2"].includes(sound) ? sound : "coin2"; - this.audio = new Audio( - new URL(`./lib/assets/sounds/${playSound}.mp3`, import.meta.url).href, - ); - this.audio.volume = 0.3; + let playSound = ["coin2", "success"].includes(sound) ? sound : "coin2"; + // Use app-hax sounds path for success sound, local path for coin + let soundPath; + if (sound === "success") { + soundPath = new URL( + `../app-hax/lib/assets/sounds/${playSound}.mp3`, + import.meta.url, + ).href; + } else { + soundPath = new URL( + `./lib/assets/sounds/${playSound}.mp3`, + import.meta.url, + ).href; + } + + this.audio = new Audio(soundPath); + this.audio.volume = 0.2; this.audio.onended = (event) => { resolve(); }; @@ -796,6 +943,8 @@ class SuperDaemon extends I18NMixin(SimpleColors) { open() { // filter to context this.opened = true; + // Reset Konami Code sequence when opening for fresh start + this.resetKonamiCode(); this.items = this.filterItems(this.allItems, this.context); const wd = this.shadowRoot.querySelector("web-dialog"); if (wd) { @@ -1160,12 +1309,16 @@ class SuperDaemon extends I18NMixin(SimpleColors) { // update this value as far as what's being typed no matter what it is this.value = e.detail.value; if (this.programName && this._programToRun) { - this.loading = true; - this.programResults = await this._programToRun( - e.detail.value, - this._programValues, - ); - this.loading = false; + // Debounce program execution to prevent flickering on rapid input + clearTimeout(this._programDebounceTimeout); + this._programDebounceTimeout = setTimeout(async () => { + this.loading = true; + this.programResults = await this._programToRun( + e.detail.value, + this._programValues, + ); + this.loading = false; + }, 150); // 150ms debounce delay } else { this.programResults = []; // we moved back out of a context, reset complete diff --git a/elements/tagging-question/_.gitignore b/elements/tagging-question/_.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/tagging-question/_.gitignore +++ b/elements/tagging-question/_.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/tagging-question/demo/index.html b/elements/tagging-question/demo/index.html index 35eeea4cac..8eea7d0976 100644 --- a/elements/tagging-question/demo/index.html +++ b/elements/tagging-question/demo/index.html @@ -4,15 +4,14 @@ TaggingQuestion: tagging-question Demo - - + +
      diff --git a/elements/terrible-themes/index.html b/elements/terrible-themes/index.html index ac10740fc2..35039dcbc4 100644 --- a/elements/terrible-themes/index.html +++ b/elements/terrible-themes/index.html @@ -4,10 +4,10 @@ terrible-themes documentation - - + + - + diff --git a/elements/terrible-themes/lib/terrible-best-themes.js b/elements/terrible-themes/lib/terrible-best-themes.js index 06dcd0902c..0d424208fb 100644 --- a/elements/terrible-themes/lib/terrible-best-themes.js +++ b/elements/terrible-themes/lib/terrible-best-themes.js @@ -20,6 +20,8 @@ import { autorun, toJS } from "mobx"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Fun, Website + * @haxcms-theme-internal false * @demo demo/index.html * @element terrible-themes */ @@ -38,11 +40,23 @@ class TerribleBestThemes extends HAXCMSRememberRoute( this.activeManifestIndex = toJS(store.activeManifestIndex); this.__disposer.push(reaction); }); - globalThis.document.body.style.backgroundColor = "#e6fbff"; } - /** - * LitElement style callback - */ + + HAXCMSGlobalStyleSheetContent() { + return [ + ...super.HAXCMSGlobalStyleSheetContent(), + css` + body { + background-color: #e6fbff; + } + body.dark-mode { + background-color: #020613; + color: #f5f5f5; + } + `, + ]; + } + static get styles() { // support for using in other classes let styles = []; @@ -55,6 +69,9 @@ class TerribleBestThemes extends HAXCMSRememberRoute( :host { display: block; } + :host([dark-mode]) { + color: #f5f5f5; + } table { padding: 25px 10vw; } @@ -64,6 +81,12 @@ class TerribleBestThemes extends HAXCMSRememberRoute( --map-menu-item-a-active-color: navy; --map-menu-item-a-active-background-color: white; } + :host([dark-mode]) site-menu { + --site-menu-active-color: #9bbcff; + --site-menu-item-active-item-color: #020613; + --map-menu-item-a-active-color: #020613; + --map-menu-item-a-active-background-color: #9bbcff; + } scroll-button { position: fixed; bottom: 0; @@ -76,6 +99,13 @@ class TerribleBestThemes extends HAXCMSRememberRoute( --scroll-button-tooltip-background-color: white; --scroll-button-tooltip-color: navy; } + :host([dark-mode]) scroll-button { + color: #9bbcff; + --scroll-button-color: #9bbcff; + --scroll-button-background-color: #020613; + --scroll-button-tooltip-background-color: #020613; + --scroll-button-tooltip-color: #9bbcff; + } `, ]; } diff --git a/elements/terrible-themes/lib/terrible-outlet-themes.js b/elements/terrible-themes/lib/terrible-outlet-themes.js index c595e13031..53209a49bd 100644 --- a/elements/terrible-themes/lib/terrible-outlet-themes.js +++ b/elements/terrible-themes/lib/terrible-outlet-themes.js @@ -21,6 +21,8 @@ import { autorun, toJS } from "mobx"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Fun, Website + * @haxcms-theme-internal false * @demo demo/index.html * @element terrible-themes */ @@ -38,11 +40,23 @@ class TerribleOutletThemes extends HAXCMSRememberRoute( this.activeManifestIndex = toJS(store.activeManifestIndex); this.__disposer.push(reaction); }); - globalThis.document.body.style.backgroundColor = "#e6fbff"; } - /** - * LitElement style callback - */ + + HAXCMSGlobalStyleSheetContent() { + return [ + ...super.HAXCMSGlobalStyleSheetContent(), + css` + body { + background-color: #e6fbff; + } + body.dark-mode { + background-color: #020613; + color: #f5f5f5; + } + `, + ]; + } + static get styles() { // support for using in other classes let styles = []; @@ -55,6 +69,9 @@ class TerribleOutletThemes extends HAXCMSRememberRoute( :host { display: block; } + :host([dark-mode]) { + color: #f5f5f5; + } site-menu { color: blue; --site-menu-active-color: blue; @@ -62,6 +79,13 @@ class TerribleOutletThemes extends HAXCMSRememberRoute( --map-menu-item-a-active-color: black; --map-menu-item-a-active-background-color: lightblue; } + :host([dark-mode]) site-menu { + color: #9bbcff; + --site-menu-active-color: #9bbcff; + --site-menu-item-active-item-color: #020613; + --map-menu-item-a-active-color: #020613; + --map-menu-item-a-active-background-color: #9bbcff; + } site-title { color: black; --site-title-link-text-decoration: none; @@ -72,6 +96,9 @@ class TerribleOutletThemes extends HAXCMSRememberRoute( --site-title-heading-text-rendering: optimizelegibility; --site-title-heading-font-weight: 100; } + :host([dark-mode]) site-title { + color: #f5f5f5; + } scroll-button { position: fixed; bottom: 0; @@ -84,6 +111,13 @@ class TerribleOutletThemes extends HAXCMSRememberRoute( --scroll-button-tooltip-background-color: white; --scroll-button-tooltip-color: black; } + :host([dark-mode]) scroll-button { + color: #9bbcff; + --scroll-button-color: #020613; + --scroll-button-background-color: #9bbcff; + --scroll-button-tooltip-background-color: #020613; + --scroll-button-tooltip-color: #9bbcff; + } `, ]; } diff --git a/elements/terrible-themes/lib/terrible-productionz-themes.js b/elements/terrible-themes/lib/terrible-productionz-themes.js index 94cbc09f46..f42e3b445f 100644 --- a/elements/terrible-themes/lib/terrible-productionz-themes.js +++ b/elements/terrible-themes/lib/terrible-productionz-themes.js @@ -19,6 +19,8 @@ import { autorun, toJS } from "mobx"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Fun, Website + * @haxcms-theme-internal false * @demo demo/index.html * @element terrible-themes */ @@ -171,6 +173,7 @@ class TerribleProductionzThemes extends HAXCMSRememberRoute( .href}/../HEADER.jpg" border="0" align="absbottom" + alt="" /> @@ -195,7 +198,8 @@ class TerribleProductionzThemes extends HAXCMSRememberRoute( border="0" width="88" height="31" - vspace="3" />
      diff --git a/elements/terrible-themes/lib/terrible-resume-themes.js b/elements/terrible-themes/lib/terrible-resume-themes.js index 17285a90b6..b0c61f5272 100644 --- a/elements/terrible-themes/lib/terrible-resume-themes.js +++ b/elements/terrible-themes/lib/terrible-resume-themes.js @@ -22,6 +22,8 @@ import { autorun, toJS } from "mobx"; * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Fun, Website + * @haxcms-theme-internal false * @demo demo/index.html * @element terrible-themes */ @@ -39,11 +41,23 @@ class TerribleResumeThemes extends HAXCMSRememberRoute( this.activeManifestIndex = toJS(store.activeManifestIndex); this.__disposer.push(reaction); }); - globalThis.document.body.style.backgroundColor = "#e6fbff"; } - /** - * LitElement style callback - */ + + HAXCMSGlobalStyleSheetContent() { + return [ + ...super.HAXCMSGlobalStyleSheetContent(), + css` + body { + background-color: #e6fbff; + } + body.dark-mode { + background-color: #020613; + color: #f5f5f5; + } + `, + ]; + } + static get styles() { // support for using in other classes let styles = []; @@ -56,6 +70,9 @@ class TerribleResumeThemes extends HAXCMSRememberRoute( :host { display: block; } + :host([dark-mode]) { + color: #f5f5f5; + } a { color: blue; text-decoration: none; @@ -72,6 +89,12 @@ class TerribleResumeThemes extends HAXCMSRememberRoute( color: red; text-decoration: none; } + :host([dark-mode]) a { + color: #9bbcff; + } + :host([dark-mode]) a:hover { + color: #ffcc66; + } a.menu { color: black; text-decoration: none; @@ -88,13 +111,22 @@ class TerribleResumeThemes extends HAXCMSRememberRoute( } site-top-menu { font-size: 18px; - --site-top-menu-bg: lightblue; + --site-top-menu-bg: #005a9e; --site-top-menu-link-color: #ffffff; --site-top-menu-indicator-color: #ffffff; --site-top-menu-link-active-color: var( --haxcms-basic-theme-accent-color ); } + :host([dark-mode]) site-top-menu { + --site-top-menu-bg: #111827; + --site-top-menu-link-color: #ffffff; + --site-top-menu-indicator-color: #ffcc66; + } + :host([dark-mode]) table[bgcolor], + :host([dark-mode]) td[bgcolor] { + background-color: #111827; + } site-top-menu::part(button) { font-size: 18px; } diff --git a/elements/terrible-themes/package.json b/elements/terrible-themes/package.json index 2886b1c130..ddb387e2ea 100644 --- a/elements/terrible-themes/package.json +++ b/elements/terrible-themes/package.json @@ -45,16 +45,13 @@ "dependencies": { "@haxtheweb/haxcms-elements": "^11.0.5", "@haxtheweb/simple-colors": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/terrible-themes/terrible-themes.js b/elements/terrible-themes/terrible-themes.js index 9dcdb3bde7..56032bc9ef 100644 --- a/elements/terrible-themes/terrible-themes.js +++ b/elements/terrible-themes/terrible-themes.js @@ -20,6 +20,8 @@ import "@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-menu-button * - HAXcms - A headless content management system * - HAXCMSTheme - A super class that provides correct baseline wiring to build a new theme * + * @haxcms-theme-category Fun, Website + * @haxcms-theme-internal false * @demo demo/index.html * @element terrible-themes */ @@ -68,6 +70,21 @@ class TerribleThemes extends HAXCMSRememberRoute( --simple-tooltip-border-radius: 0; } + :host([dark-mode]) { + background: #111111; + color: #f5f7f9; + } + + :host([dark-mode]) table { + background-color: #111111; + color: inherit; + } + + :host([dark-mode]) tr[bgcolor], + :host([dark-mode]) td[bgcolor] { + background-color: transparent; + } + site-active-title { display: block; padding: 0; @@ -110,6 +127,12 @@ class TerribleThemes extends HAXCMSRememberRoute( -ms-grid-column-align: stretch; -webkit-box-direction: normal; } + :host([dark-mode]) site-menu-button { + color: #f5f7f9; + border-color: #333333; + background-color: #1a1a1a; + box-shadow: 0 3px 8px 0 rgba(0, 0, 0, 0.6); + } site-menu-button div.wrapper { flex: 1; margin: 0; @@ -131,6 +154,9 @@ class TerribleThemes extends HAXCMSRememberRoute( simple-datetime { color: #444444; } + :host([dark-mode]) simple-datetime { + color: #cccccc; + } site-menu-button div .bottom { font-size: 16px; font-weight: 500; @@ -201,6 +227,12 @@ class TerribleThemes extends HAXCMSRememberRoute( color: #4444ff; text-decoration: underline; } + :host([dark-mode]) a { + color: #9bbcff; + } + :host([dark-mode]) a:hover { + color: #ff8ad4; + } /*Log out link at top right*/ a.loginState:visited { color: #0000ff; @@ -227,8 +259,19 @@ class TerribleThemes extends HAXCMSRememberRoute( --haxcms-basic-theme-accent-color ); } + :host([dark-mode]) site-top-menu { + --site-top-menu-bg: #222233; + --site-top-menu-link-color: #ffffff; + --site-top-menu-indicator-color: #ff8ad4; + } site-top-menu::part(button) { font-size: 18px; + background-color: pink; + color: #000000; + } + :host([dark-mode]) site-top-menu::part(button) { + background-color: #222233; + color: #ffffff; } `, ]; @@ -239,6 +282,7 @@ class TerribleThemes extends HAXCMSRememberRoute( render() { return html`
      -
      +
      -
      +
      TrainingTheme: training-theme Demo - - +
      diff --git a/elements/training-theme/index.html b/elements/training-theme/index.html index c295e483eb..cc5c606922 100644 --- a/elements/training-theme/index.html +++ b/elements/training-theme/index.html @@ -4,10 +4,10 @@ training-theme documentation - - + + - + diff --git a/elements/training-theme/package.json b/elements/training-theme/package.json index 12ab5a0e69..626752efcd 100644 --- a/elements/training-theme/package.json +++ b/elements/training-theme/package.json @@ -45,16 +45,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/haxcms-elements": "^11.0.5", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "gulp-babel": "8.0.0", "polymer-build": "3.1.4", diff --git a/elements/twitter-embed/.gitignore b/elements/twitter-embed/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/twitter-embed/.gitignore +++ b/elements/twitter-embed/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/twitter-embed/custom-elements.json b/elements/twitter-embed/custom-elements.json deleted file mode 100644 index 162d11d1bd..0000000000 --- a/elements/twitter-embed/custom-elements.json +++ /dev/null @@ -1,333 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "twitter-embed.js", - "declarations": [ - { - "kind": "class", - "description": "`twitter-embed`\n`A simple way to embed tweets from twitter without their whole API, with LitElement", - "name": "TwitterEmbed", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "description": "haxProperties integration via file reference", - "readonly": true - }, - { - "kind": "method", - "name": "haxHooks", - "description": "Implements haxHooks to tie into life-cycle if hax exists." - }, - { - "kind": "method", - "name": "haxgizmoRegistration", - "parameters": [ - { - "name": "store" - } - ], - "description": "Supply translations for the UI elements of HAX in meme-maker" - }, - { - "kind": "method", - "name": "haxactiveElementChanged", - "parameters": [ - { - "name": "el" - }, - { - "name": "val" - } - ], - "description": "double-check that we are set to inactivate click handlers\nthis is for when activated in a duplicate / adding new content state" - }, - { - "kind": "method", - "name": "haxeditModeChanged", - "parameters": [ - { - "name": "val" - } - ], - "description": "Set a flag to test if we should block link clicking on the entire card\notherwise when editing in hax you can't actually edit it bc its all clickable.\nif editMode goes off this helps ensure we also become clickable again" - }, - { - "kind": "method", - "name": "_clickPrevent", - "parameters": [ - { - "name": "e" - } - ], - "description": "special support for HAX since the whole card is selectable" - }, - { - "kind": "field", - "name": "lang", - "privacy": "public", - "type": { - "text": "string" - }, - "attribute": "lang" - }, - { - "kind": "field", - "name": "dataWidth", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"550px\"", - "attribute": "data-width" - }, - { - "kind": "field", - "name": "dataTheme", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"light\"", - "attribute": "data-theme" - }, - { - "kind": "field", - "name": "tweet", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "tweet" - }, - { - "kind": "field", - "name": "tweetId", - "privacy": "public", - "type": { - "text": "null" - }, - "default": "null", - "attribute": "tweet-id" - }, - { - "kind": "field", - "name": "allowPopups", - "privacy": "public", - "type": { - "text": "string" - }, - "default": "\"allow-popups\"", - "attribute": "allowPopups" - }, - { - "kind": "field", - "name": "_haxstate", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "_haxstate" - }, - { - "kind": "field", - "name": "noPopups", - "privacy": "public", - "type": { - "text": "boolean" - }, - "attribute": "no-popups" - } - ], - "attributes": [ - { - "name": "tweet", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "tweet" - }, - { - "name": "_haxstate", - "type": { - "text": "boolean" - }, - "fieldName": "_haxstate" - }, - { - "name": "lang", - "type": { - "text": "string" - }, - "fieldName": "lang" - }, - { - "name": "data-width", - "type": { - "text": "string" - }, - "default": "\"550px\"", - "fieldName": "dataWidth" - }, - { - "name": "data-theme", - "type": { - "text": "string" - }, - "default": "\"light\"", - "fieldName": "dataTheme" - }, - { - "name": "tweet-id", - "type": { - "text": "null" - }, - "default": "null", - "fieldName": "tweetId" - }, - { - "name": "no-popups", - "type": { - "text": "boolean" - }, - "fieldName": "noPopups" - }, - { - "name": "allowPopups", - "type": { - "text": "string" - }, - "default": "\"allow-popups\"", - "fieldName": "allowPopups" - } - ], - "superclass": { - "name": "LitElement", - "package": "lit" - }, - "tagName": "twitter-embed", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "TwitterEmbed", - "module": "twitter-embed.js" - } - }, - { - "kind": "js", - "name": "TwitterEmbed", - "declaration": { - "name": "TwitterEmbed", - "module": "twitter-embed.js" - } - } - ] - }, - { - "kind": "javascript-module", - "path": "lib/twitter-embed-vanilla.js", - "declarations": [ - { - "kind": "class", - "description": "", - "name": "TwitterEmbedVanilla", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "dataWidth" - }, - { - "kind": "field", - "name": "dataTheme" - }, - { - "kind": "field", - "name": "lang" - }, - { - "kind": "field", - "name": "tweetId" - }, - { - "kind": "field", - "name": "tweet" - }, - { - "kind": "field", - "name": "html", - "description": "my own convention, easy to remember", - "readonly": true - }, - { - "kind": "field", - "name": "allowPopups" - } - ], - "attributes": [ - { - "name": "lang" - }, - { - "name": "tweet" - }, - { - "name": "data-width" - }, - { - "name": "data-theme" - }, - { - "name": "tweet-id" - }, - { - "name": "no-popups" - } - ], - "superclass": { - "name": "HTMLElement" - }, - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "TwitterEmbedVanilla", - "module": "lib/twitter-embed-vanilla.js" - } - }, - { - "kind": "js", - "name": "TwitterEmbedVanilla", - "declaration": { - "name": "TwitterEmbedVanilla", - "module": "lib/twitter-embed-vanilla.js" - } - } - ] - } - ] -} diff --git a/elements/twitter-embed/demo/index.html b/elements/twitter-embed/demo/index.html index 5a9a619e99..10187aae37 100644 --- a/elements/twitter-embed/demo/index.html +++ b/elements/twitter-embed/demo/index.html @@ -4,16 +4,15 @@ TwitterEmbed: twitter-embed Demo - - +
      diff --git a/elements/twitter-embed/index.html b/elements/twitter-embed/index.html index cede38763c..bd2d241640 100644 --- a/elements/twitter-embed/index.html +++ b/elements/twitter-embed/index.html @@ -4,10 +4,10 @@ twitter-embed documentation - - + + - + diff --git a/elements/twitter-embed/package.json b/elements/twitter-embed/package.json index dd76b74098..6748994eb6 100644 --- a/elements/twitter-embed/package.json +++ b/elements/twitter-embed/package.json @@ -39,16 +39,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/twitter-embed/twitter-embed.js b/elements/twitter-embed/twitter-embed.js index bd4e39b98b..4f81ecd5f7 100644 --- a/elements/twitter-embed/twitter-embed.js +++ b/elements/twitter-embed/twitter-embed.js @@ -95,7 +95,6 @@ class TwitterEmbed extends LitElement { "./locales/twitter-embed.haxProperties.es.json", import.meta.url, ).href + "/../", - }, }), ); diff --git a/elements/type-writer/.gitignore b/elements/type-writer/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/type-writer/.gitignore +++ b/elements/type-writer/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/type-writer/custom-elements.json b/elements/type-writer/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/type-writer/demo/index.html b/elements/type-writer/demo/index.html index 209907da7e..491111ab12 100644 --- a/elements/type-writer/demo/index.html +++ b/elements/type-writer/demo/index.html @@ -4,15 +4,14 @@ TypeWriter: type-writer Demo - - +
      diff --git a/elements/type-writer/index.html b/elements/type-writer/index.html index dbd5ca3b45..7a2b7ade89 100755 --- a/elements/type-writer/index.html +++ b/elements/type-writer/index.html @@ -4,10 +4,10 @@ type-writer documentation - - + + - + diff --git a/elements/type-writer/package.json b/elements/type-writer/package.json old mode 100755 new mode 100644 index 5b207d43cc..0ae8329933 --- a/elements/type-writer/package.json +++ b/elements/type-writer/package.json @@ -41,16 +41,13 @@ "license": "Apache-2.0", "dependencies": { "@haxtheweb/intersection-element": "^11.0.0", - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/un-sdg/package.json b/elements/un-sdg/package.json index 08a4c380f4..634cc4a266 100644 --- a/elements/un-sdg/package.json +++ b/elements/un-sdg/package.json @@ -29,7 +29,7 @@ "test:watch": "web-test-runner test/**/*.test.js --node-resolve --watch" }, "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@babel/preset-env": "^7.16.4", diff --git a/elements/undo-manager/.gitignore b/elements/undo-manager/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/undo-manager/.gitignore +++ b/elements/undo-manager/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/undo-manager/demo/index.html b/elements/undo-manager/demo/index.html index fd80d2e76b..0bb661c6c8 100644 --- a/elements/undo-manager/demo/index.html +++ b/elements/undo-manager/demo/index.html @@ -10,7 +10,7 @@ - + + - + diff --git a/elements/undo-manager/package.json b/elements/undo-manager/package.json old mode 100755 new mode 100644 index e63b457c6b..ba297fc7eb --- a/elements/undo-manager/package.json +++ b/elements/undo-manager/package.json @@ -40,16 +40,13 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0" + "lit": "3.3.1" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/unity-webgl/.gitignore b/elements/unity-webgl/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/unity-webgl/.gitignore +++ b/elements/unity-webgl/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/unity-webgl/custom-elements.json b/elements/unity-webgl/custom-elements.json deleted file mode 100644 index bace77c65c..0000000000 --- a/elements/unity-webgl/custom-elements.json +++ /dev/null @@ -1,100 +0,0 @@ -{ - "schemaVersion": "1.0.0", - "readme": "", - "modules": [ - { - "kind": "javascript-module", - "path": "unity-webgl.js", - "declarations": [ - { - "kind": "class", - "description": "`unity-webgl`\n`Unity WebGL player`", - "name": "UnityWebgl", - "members": [ - { - "kind": "field", - "name": "tag", - "static": true, - "description": "This is a convention, not the standard", - "readonly": true - }, - { - "kind": "field", - "name": "html", - "description": "This is a convention, not the standard to return HTML of the element", - "readonly": true - }, - { - "kind": "field", - "name": "target" - }, - { - "kind": "field", - "name": "compression" - }, - { - "kind": "field", - "name": "streamingurl" - }, - { - "kind": "field", - "name": "companyname" - }, - { - "kind": "field", - "name": "productname" - }, - { - "kind": "field", - "name": "productversion" - }, - { - "kind": "field", - "name": "width" - }, - { - "kind": "field", - "name": "height" - }, - { - "kind": "field", - "name": "background" - }, - { - "kind": "field", - "name": "haxProperties", - "static": true, - "readonly": true - }, - { - "kind": "field", - "name": "template" - } - ], - "superclass": { - "name": "HTMLElement" - }, - "tagName": "unity-webgl", - "customElement": true - } - ], - "exports": [ - { - "kind": "custom-element-definition", - "declaration": { - "name": "UnityWebgl", - "module": "unity-webgl.js" - } - }, - { - "kind": "js", - "name": "UnityWebgl", - "declaration": { - "name": "UnityWebgl", - "module": "unity-webgl.js" - } - } - ] - } - ] -} diff --git a/elements/unity-webgl/demo/index.html b/elements/unity-webgl/demo/index.html index 5236a0de3a..49cbebc240 100644 --- a/elements/unity-webgl/demo/index.html +++ b/elements/unity-webgl/demo/index.html @@ -4,15 +4,14 @@ UnityWebgl: unity-webgl Demo - - +
      diff --git a/elements/unity-webgl/index.html b/elements/unity-webgl/index.html index 156e15e950..485cbf0eee 100644 --- a/elements/unity-webgl/index.html +++ b/elements/unity-webgl/index.html @@ -4,10 +4,10 @@ unity-webgl documentation - - + + - + diff --git a/elements/unity-webgl/package.json b/elements/unity-webgl/package.json index cd3efae888..6fcb96f3e1 100644 --- a/elements/unity-webgl/package.json +++ b/elements/unity-webgl/package.json @@ -47,10 +47,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/user-action/.gitignore b/elements/user-action/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/user-action/.gitignore +++ b/elements/user-action/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/user-action/custom-elements.json b/elements/user-action/custom-elements.json old mode 100755 new mode 100644 diff --git a/elements/user-action/demo/index.html b/elements/user-action/demo/index.html index fd1436591d..ae410d762b 100644 --- a/elements/user-action/demo/index.html +++ b/elements/user-action/demo/index.html @@ -9,10 +9,10 @@ - + - + + - + diff --git a/elements/user-action/package.json b/elements/user-action/package.json old mode 100755 new mode 100644 index 548dee839c..4aaecf2044 --- a/elements/user-action/package.json +++ b/elements/user-action/package.json @@ -44,10 +44,7 @@ "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "wct-browser-legacy": "1.0.2" }, diff --git a/elements/user-action/user-action.js b/elements/user-action/user-action.js index 50768160a5..806d4eded6 100644 --- a/elements/user-action/user-action.js +++ b/elements/user-action/user-action.js @@ -140,7 +140,6 @@ class UserAction extends HTMLElement { "./locales/user-action.haxProperties.es.json", import.meta.url, ).href + "/../", - }, }), ); diff --git a/elements/user-scaffold/.gitignore b/elements/user-scaffold/.gitignore index bddbc94db5..5456ae68cd 100644 --- a/elements/user-scaffold/.gitignore +++ b/elements/user-scaffold/.gitignore @@ -1,2 +1,26 @@ -node_modules -analysis-error.json \ No newline at end of file +## editors +/.idea +/.vscode + +## system files +.DS_Store + +## npm +/node_modules/ +/npm-debug.log + +## testing +/coverage/ + +## temp folders +/.tmp/ + +# build +/_site/ +/dist/ +/out-tsc/ +/public/ + +storybook-static +custom-elements.json +.vercel \ No newline at end of file diff --git a/elements/user-scaffold/demo/index.html b/elements/user-scaffold/demo/index.html index 97847bc814..6614e6d7ad 100644 --- a/elements/user-scaffold/demo/index.html +++ b/elements/user-scaffold/demo/index.html @@ -4,14 +4,13 @@ UserScaffold: user-scaffold Demo - - +
      diff --git a/elements/user-scaffold/index.html b/elements/user-scaffold/index.html index d35e24a9d9..00985c3986 100644 --- a/elements/user-scaffold/index.html +++ b/elements/user-scaffold/index.html @@ -4,10 +4,10 @@ user-scaffold documentation - - + + - + diff --git a/elements/user-scaffold/package.json b/elements/user-scaffold/package.json index 7d9d7e11a8..c82531503c 100644 --- a/elements/user-scaffold/package.json +++ b/elements/user-scaffold/package.json @@ -44,17 +44,14 @@ }, "license": "Apache-2.0", "dependencies": { - "lit": "3.3.0", + "lit": "3.3.1", "mobx": "6.13.7" }, "devDependencies": { "@custom-elements-manifest/analyzer": "0.10.4", "@haxtheweb/deduping-fix": "^11.0.0", "@open-wc/testing": "4.0.0", - "@polymer/iron-component-page": "github:PolymerElements/iron-component-page", - "@haxtheweb/utils": "^11.0.0", "@web/dev-server": "0.4.6", - "@webcomponents/webcomponentsjs": "^2.8.0", "concurrently": "9.1.2", "gulp-babel": "8.0.0", "polymer-build": "3.1.4", diff --git a/elements/utils/demo/index.html b/elements/utils/demo/index.html index 4761b1e316..7ccb9c5666 100644 --- a/elements/utils/demo/index.html +++ b/elements/utils/demo/index.html @@ -4,11 +4,10 @@ Demo Snippet: demo-snippet Demo - + + +
      +

      Video Player: Audio Description Demo

      + +
      +

      About Audio Descriptions

      +

      + Audio descriptions provide narration of important visual elements for users who are blind or have low vision. + This demo shows how to add an MP3 audio description track that syncs with video playback. +

      +

      + Features: +

      +
        +
      • Synchronized playback with video
      • +
      • Automatic drift correction (resyncs if audio/video drift exceeds 200ms)
      • +
      • Toggle on/off with preference saved to localStorage
      • +
      • Independent volume control
      • +
      • Settings menu checkbox - Toggle appears in player settings when audio description is available
      • +
      +

      + How to use: Click the settings (gear) icon in the video player controls to access the audio description toggle. +

      +
      + +

      Video with Audio Description Track

      + + + + +
      +

      Implementation Notes

      +
        +
      • Property: audio-description-source accepts a URL to an MP3 file
      • +
      • Property: audio-description-enabled controls the toggle state
      • +
      • Method: toggleAudioDescription() programmatically toggles the feature
      • +
      • LocalStorage Key: Preference is saved as video-player-ad-{source}
      • +
      • Default: Audio description is OFF by default, even when a track is provided
      • +
      +
      +
      + + diff --git a/elements/video-player/demo/index.html b/elements/video-player/demo/index.html index 198be8d872..030413ada7 100644 --- a/elements/video-player/demo/index.html +++ b/elements/video-player/demo/index.html @@ -9,10 +9,10 @@ - + + \n
      \n \n
      \n

      Basic page-flag demo

      \n \n This is a comment\n This is a comment\n This is a comment\n \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n \n This is a comment\n This is a comment\n This is a comment\n \n

      Here's a bunch of content

      \n
      \n
      \n

      Basic page-flag demo

      \n \n This is a comment\n This is a comment\n This is a comment\n \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n \n This is a comment\n This is a comment\n This is a comment\n \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n

      Here's a bunch of content

      \n \n This is a comment\n This is a comment\n This is a comment\n \n

      Here's a bunch of content

      \n
      \n
      \n
      `,\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"page-flag passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"page-flag passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"page-flag can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: label, opened, show" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "page-scroll-position": { - "element": "page-scroll-position", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "attachedCallback" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../page-scroll-position.js\";\n\ndescribe(\"page-scroll-position test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"page-scroll-position passes accessibility test\", async () => {\n const el = await fixture(\n html` `\n );\n await expect(el).to.be.accessible();\n });\n it(\"page-scroll-position passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"page-scroll-position can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "page-section": { - "element": "page-section", - "component": { - "properties": [ - { - "name": "anchor", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "null" - }, - { - "name": "scrollerLabel", - "type": "String", - "attribute": "scroller-label", - "config": "type: String, attribute: \"scroller-label\"", - "defaultValue": "\"Scroll to reveal content\"" - }, - { - "name": "filter", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "fold", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "full", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "scroller", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "bg", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "null" - }, - { - "name": "image", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true", - "defaultValue": "null" - }, - { - "name": "preset", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true", - "defaultValue": "null" - } - ], - "slots": [ - "default", - "entice", - "buttons" - ], - "events": [], - "methods": [ - "if", - "cleanAnchor", - "videoPlay", - "videoPause", - "switch", - "bgStyle", - "scrollToNextTarget" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../page-section.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: anchor, scrollerLabel, filter, fold, full, scroller, bg, image, preset" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default, entice, buttons" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: scroller-label" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "paper-input-flagged": { - "element": "paper-input-flagged", - "component": { - "properties": [ - { - "name": "label", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "disabled", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "maxlength", - "type": "Number", - "attribute": null, - "config": "type: Number," - }, - { - "name": "minlength", - "type": "Number", - "attribute": null, - "config": "type: Number," - }, - { - "name": "status", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true," - }, - { - "name": "value", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "flaggedInput", - "type": "Array", - "attribute": "flagged-input", - "config": "type: Array,\n attribute: \"flagged-input\"," - }, - { - "name": "inputSuccess", - "type": "Object", - "attribute": "input-success", - "config": "type: Object,\n attribute: \"input-success\"," - }, - { - "name": "__activeMessage", - "type": "String", - "attribute": null, - "config": "type: String," - } - ], - "slots": [], - "events": [], - "methods": [ - "if", - "valueEvent", - "testStatus", - "for", - "switch" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../paper-input-flagged.js\";\n\ndescribe(\"paper-input-flagged test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html` `,\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"paper-input-flagged passes accessibility test\", async () => {\n const el = await fixture(\n html` `\n );\n await expect(el).to.be.accessible();\n });\n it(\"paper-input-flagged passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"paper-input-flagged can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: label, disabled, icon, maxlength, minlength, status, value, flaggedInput, inputSuccess, __activeMessage" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "paper-stepper": { - "element": "paper-stepper", - "component": { - "properties": [ - { - "name": "selected", - "type": "Number", - "attribute": null, - "config": "type: Number,\n notify: true,\n value: 0," - }, - { - "name": "progressBar", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n value: false," - }, - { - "name": "backLabel", - "type": "String", - "attribute": null, - "config": "type: String,\n value: \"Back\"," - }, - { - "name": "nextLabel", - "type": "String", - "attribute": null, - "config": "type: String,\n value: \"Next\"," - }, - { - "name": "disablePrevious", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n value: false," - }, - { - "name": "disableNext", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n value: false," - }, - { - "name": "noButtons", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n value: false," - } - ], - "slots": [], - "events": [], - "methods": [], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../paper-stepper.js\";\n\ndescribe(\"paper-stepper test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"paper-stepper passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"paper-stepper passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"paper-stepper can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: selected, progressBar, backLabel, nextLabel, disablePrevious, disableNext, noButtons" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "parallax-image": { - "element": "parallax-image", - "component": { - "properties": [ - { - "name": "imageBg", - "type": "String", - "attribute": "image-bg", - "config": "type: String,\n attribute: \"image-bg\",\n reflect: true,", - "defaultValue": "\"\"" - }, - { - "name": "describedBy", - "type": "String", - "attribute": "described-by", - "config": "type: String,\n attribute: \"described-by\"," - } - ], - "slots": [ - "parallax_heading" - ], - "events": [], - "methods": [ - "if", - "scrollBy" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SchemaBehaviors" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../parallax-image.js\";\n\ndescribe(\"parallax-image test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"parallax-image passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"parallax-image passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"parallax-image can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: imageBg, describedBy" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: parallax_heading" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "pdf-browser-viewer": { - "element": "pdf-browser-viewer", - "component": { - "properties": [ - { - "name": "file", - "type": "String", - "attribute": null, - "config": "type: String,\n value: undefined,\n reflectToAttribute: true," - }, - { - "name": "notSupportedMessage", - "type": "String", - "attribute": null, - "config": "type: String,\n value:\n \"It appears your Web browser is not configured to display PDF files. No worries, just\"," - }, - { - "name": "notSupportedLinkMessage", - "type": "String", - "attribute": null, - "config": "type: String,\n value: \"click here to download the PDF file.\"," - }, - { - "name": "height", - "type": "String", - "attribute": null, - "config": "type: String,\n value: \"400px\"," - }, - { - "name": "width", - "type": "String", - "attribute": null, - "config": "type: String,\n value: \"100%\"," - }, - { - "name": "card", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n value: false," - }, - { - "name": "downloadLabel", - "type": "String", - "attribute": null, - "config": "type: String,\n value: \"Download\"," - }, - { - "name": "elevation", - "type": "String", - "attribute": null, - "config": "type: String,\n value: \"1\"," - } - ], - "slots": [], - "events": [], - "methods": [ - "clear" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../pdf-browser-viewer.js\";\n\ndescribe(\"pdf-browser-viewer test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"pdf-browser-viewer passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"pdf-browser-viewer passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"pdf-browser-viewer can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: file, notSupportedMessage, notSupportedLinkMessage, height, width, card, downloadLabel, elevation" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "person-testimonial": { - "element": "person-testimonial", - "component": { - "properties": [ - { - "name": "describedBy", - "type": "String", - "attribute": "described-by", - "config": "type: String,\n attribute: \"described-by\"," - }, - { - "name": "image", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "name", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "position", - "type": "String", - "attribute": null, - "config": "type: String," - } - ], - "slots": [], - "events": [], - "methods": [], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../person-testimonial.js\";\n\ndescribe(\"person-testimonial test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"person-testimonial passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"person-testimonial passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"person-testimonial can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: describedBy, image, name, position" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "place-holder": { - "element": "place-holder", - "component": { - "properties": [ - { - "name": "iconFromType", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"editor:format-align-left\"" - }, - { - "name": "text", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "directions", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"Drag and drop file to replace\"" - }, - { - "name": "calcText", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "type", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"text\"" - }, - { - "name": "dragOver", - "type": "Boolean", - "attribute": "drag-over", - "config": "type: Boolean,\n reflect: true,\n attribute: \"drag-over\",", - "defaultValue": "false" - } - ], - "slots": [], - "events": [ - "place-holder-replace", - "place-holder-file-drop" - ], - "methods": [ - "if", - "fireReplaceEvent", - "switch" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../place-holder.js\";\n\ndescribe(\"place-holder test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: iconFromType, text, directions, calcText, type, dragOver" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: place-holder-replace, place-holder-file-drop" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: drag-over" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "play-list": { - "element": "play-list", - "component": { - "properties": [ - { - "name": "items", - "type": "Array", - "attribute": null, - "config": "type: Array", - "defaultValue": "[]" - }, - { - "name": "loop", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "edit", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "navigation", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "pagination", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "aspectRatio", - "type": "String", - "attribute": "aspect-ratio", - "config": "type: String, reflect: true, attribute: \"aspect-ratio\"", - "defaultValue": "\"16:9\"" - }, - { - "name": "orientation", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true", - "defaultValue": "\"horizontal\"" - }, - { - "name": "slide", - "type": "Number", - "attribute": null, - "config": "type: Number, reflect: true", - "defaultValue": "0" - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if", - "renderHAXItem", - "slideIndexChanged", - "haxHooks", - "haxClickSlideIndex", - "haxinlineContextMenu", - "haxToggleEdit" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../play-list.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: items, loop, edit, navigation, pagination, aspectRatio, orientation, slide" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: aspect-ratio" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "polaris-theme": { - "element": "polaris-theme", - "component": { - "properties": [ - { - "name": "searchTerm", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "siteDescription", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "imageLink", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "image", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "imageAlt", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "pageTimestamp", - "type": "Number", - "attribute": null, - "config": "type: Number," - } - ], - "slots": [], - "events": [], - "methods": [ - "if", - "appStoreReady", - "siteModalClick", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "HAXCMSOperationButtons" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../polaris-theme.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: searchTerm, siteDescription, imageLink, image, imageAlt, pageTimestamp" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "portal-launcher": { - "element": "portal-launcher", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "normalizeEventPath", - "if", - "click" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../portal-launcher.js\";\n\ndescribe(\"portal-launcher test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"portal-launcher passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"portal-launcher passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"portal-launcher can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "post-card": { - "element": "post-card", - "component": { - "properties": [ - { - "name": "t", - "type": "Object", - "attribute": null, - "config": "type: Object" - }, - { - "name": "to", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true" - }, - { - "name": "from", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true" - }, - { - "name": "message", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true" - }, - { - "name": "photoSrc", - "type": "String", - "attribute": "photo-src", - "config": "type: String, attribute: \"photo-src\", reflect: true" - }, - { - "name": "stampSrc", - "type": "String", - "attribute": "stamp-src", - "config": "type: String, attribute: \"stamp-src\", reflect: true" - }, - { - "name": "postMarkLocations", - "type": "String", - "attribute": "post-mark-locations", - "config": "type: String,\n reflect: true,\n attribute: \"post-mark-locations\"," - } - ], - "slots": [ - "to", - "from", - "message" - ], - "events": [ - "i18n-manager-register-element" - ], - "methods": [], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": true, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { html } from \"lit\";\nimport { fixture, expect } from \"@open-wc/testing\";\n\nimport \"../post-card.js\";\n\ndescribe(\"PostCard\", () => {\n let element;\n\n beforeEach(async () => {\n element = await fixture(\n html``,\n );\n });\n\n it(\"renders an h3\", () => {\n const h3 = element.shadowRoot.querySelector(\"h3\");\n expect(h3).to.exist;\n });\n\n it(\"renders a post-card-photo\", () => {\n const pcp = element.shadowRoot.querySelector(\"post-card-photo\");\n expect(pcp).to.exist;\n });\n\n it(\"renders a post-card-stamp\", () => {\n const pcs = element.shadowRoot.querySelector(\"post-card-stamp\");\n expect(pcs).to.exist;\n });\n\n it(\"renders a correct To address\", () => {\n const to = element.shadowRoot.querySelector('slot[name=\"to\"]');\n expect(to).to.exist;\n expect(to.textContent).to.equal(\"Future\");\n });\n\n it(\"renders a correct from address\", () => {\n const from = element.shadowRoot.querySelector('slot[name=\"from\"]');\n expect(from).to.exist;\n expect(from.textContent).to.equal(\"Past\");\n });\n\n it(\"renders a correct message\", () => {\n const mess = element.shadowRoot.querySelector('slot[name=\"message\"]');\n expect(mess).to.exist;\n expect(mess.textContent).to.equal(\"To make a baby....\");\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\ndescribe(\"PostCardPostmark\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html``,\n );\n });\n\n it(\"renders a location\", async () => {\n const loco = element.shadowRoot.querySelector(\"p\");\n expect(loco).to.exist;\n // expect(loco.textContent).to.equal('Europe')\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: t, to, from, message, photoSrc, stampSrc, postMarkLocations" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: photo-src, stamp-src, post-mark-locations" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "pouch-db": { - "element": "pouch-db", - "component": { - "properties": [], - "slots": [], - "events": [ - "pouch-db-show-data" - ], - "methods": [ - "if", - "userEngagmentFunction", - "switch", - "function", - "getDataFunction", - "processxAPI", - "processItems" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../pouch-db.js\";\n\ndescribe(\"pouch-db test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html` `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"pouch-db passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"pouch-db passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"pouch-db can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: pouch-db-show-data" - } - ], - "needsWork": true, - "priority": "MEDIUM" - }, - "product-card": { - "element": "product-card", - "component": { - "properties": [ - { - "name": "disabled", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "heading", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "subheading", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "hasDemo", - "type": "Boolean", - "attribute": "has-demo", - "config": "type: Boolean,\n attribute: \"has-demo\"," - } - ], - "slots": [], - "events": [ - "product-card-demo-show" - ], - "methods": [], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../product-card.js\";\n\ndescribe(\"product-card test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"product-card passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"product-card passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"product-card can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: disabled, heading, subheading, icon, hasDemo" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: product-card-demo-show" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "product-glance": { - "element": "product-glance", - "component": { - "properties": [ - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "subtitle", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String" - } - ], - "slots": [], - "events": [], - "methods": [ - "if" - ], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../product-glance.js\";\n\ndescribe(\"product-glance test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"product-glance passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"product-glance passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"product-glance can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, subtitle, icon" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "product-offering": { - "element": "product-offering", - "component": { - "properties": [ - { - "name": "alt", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "\"\"" - }, - { - "name": "source", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "_titleOne", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "_titleTwo", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "description", - "type": "String", - "attribute": null, - "config": "type: String" - } - ], - "slots": [ - "description" - ], - "events": [], - "methods": [ - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "IntersectionObserverMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../product-offering.js\";\n\ndescribe(\"product-offering test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"product-offering passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"product-offering passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"product-offering can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alt, source, icon, title, _titleOne, _titleTwo, description" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: description" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "progress-donut": { - "element": "progress-donut", - "component": { - "properties": [ - { - "name": "animation", - "type": "Number", - "attribute": "animation", - "config": "type: Number,\n attribute: \"animation\",", - "defaultValue": "-1" - }, - { - "name": "animationDelay", - "type": "Number", - "attribute": "animation-delay", - "config": "type: Number,\n attribute: \"animation-delay\",", - "defaultValue": "0" - }, - { - "name": "complete", - "type": "Array", - "attribute": null, - "config": "type: Array,", - "defaultValue": "[]" - }, - { - "name": "desc", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "imageSrc", - "type": "String", - "attribute": "image-src", - "config": "attribute: \"image-src\",\n type: String,\n reflect: true,", - "defaultValue": "\"\"" - }, - { - "name": "imageAlt", - "type": "String", - "attribute": "image-alt", - "config": "attribute: \"image-alt\",\n type: String,\n reflect: true,", - "defaultValue": "\"\"" - } - ], - "slots": [], - "events": [], - "methods": [ - "addAnimation", - "if" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../progress-donut.js\";\n\ndescribe(\"progress-donut test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html`\n `,\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"progress-donut passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"progress-donut passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"progress-donut can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: animation, animationDelay, complete, desc, imageSrc, imageAlt" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "promise-progress": { - "element": "promise-progress", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SimpleColorsSuper" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../promise-progress.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "q-r": { - "element": "q-r", - "component": { - "properties": [ - { - "name": "data", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "modulesize", - "type": "Number", - "attribute": null, - "config": "type: Number,", - "defaultValue": "4" - }, - { - "name": "margin", - "type": "Number", - "attribute": null, - "config": "type: Number,", - "defaultValue": "2" - }, - { - "name": "format", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"png\"" - } - ], - "slots": [], - "events": [], - "methods": [], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../q-r.js\";\n\ndescribe(\"q-r test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html` `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"q-r passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"q-r passes accessibility negation\", async () => {\n const el = await fixture(html``);\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"q-r can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: data, title, modulesize, margin, format" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "radio-behaviors": { - "element": "radio-behaviors", - "component": { - "properties": [ - { - "name": "itemData", - "type": "Array", - "attribute": null, - "config": "type: Array," - }, - { - "name": "selection", - "type": "String", - "attribute": "selection", - "config": "type: String,\n attribute: \"selection\"," - } - ], - "slots": [], - "events": [ - "selection-changed" - ], - "methods": [ - "selectItem", - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../radio-behaviors.js\";\n\ndescribe(\"radio-behaviors test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"radio-behaviors passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"radio-behaviors passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"radio-behaviors can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: itemData, selection" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: selection-changed" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "relative-heading": { - "element": "relative-heading", - "component": { - "properties": [ - { - "name": "closeIcon", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "closeLabel", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "copyMessage", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "disableLink", - "type": "Boolean", - "attribute": "disable-link", - "config": "type: Boolean,\n attribute: \"disable-link\",", - "defaultValue": "false" - }, - { - "name": "linkAlignRight", - "type": "Boolean", - "attribute": "link-align-right", - "config": "type: Boolean,\n attribute: \"link-align-right\",\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "linkIcon", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"link\"" - }, - { - "name": "linkLabel", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"Get link\"" - } - ], - "slots": [], - "events": [], - "methods": [], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../relative-heading.js\";\n\ndescribe(\"relative-heading test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html`\n

      Lorem ipsum dolor

      \n
      \n

      Lorem ipsum dolor sit amet, consectetur adipiscing elit.

      \n\n \n

      Praesent ultrices

      \n
      \n

      \n Mauris aliquam lorem justo. Praesent ultrices lorem nec est iaculis\n viverra dignissim eu neque. Nullam vitae nisl diam.\n

      \n\n \n

      Suspendisse

      \n
      \n

      \n Suspendisse potenti. Nulla venenatis porta felis id feugiat. Vivamus\n vehicula molestie sapien hendrerit ultricies.\n

      \n\n \n

      Sapien sit amet

      \n
      \n

      \n Quisque volutpat eu sapien sit amet interdum. Proin venenatis tellus\n eu nisi congue aliquet.\n

      \n\n \n
      Sollicitudin
      \n
      \n

      \n Nullam at velit sollicitudin, porta mi quis, lacinia velit. Praesent\n quis mauris sem.\n

      \n\n \n
      In et volutpat
      \n
      \n

      \n In et volutpat nisi. Suspendisse vel nibh eu magna posuere\n sollicitudin. Praesent ac ex varius, facilisis urna et, cursus tellus.\n

      `,\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"relative-heading passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"relative-heading passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"relative-heading can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: closeIcon, closeLabel, copyMessage, disableLink, linkAlignRight, linkIcon, linkLabel" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "replace-tag": { - "element": "replace-tag", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "performanceBasedReplacement", - "handleIntersectionCallback", - "for", - "runReplacement" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [ - "ReplaceTagSuper" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../replace-tag.js\";\n\ndescribe(\"replace-tag test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html`will replace `,\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"replace-tag passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"replace-tag passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"replace-tag can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "responsive-grid": { - "element": "responsive-grid", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../responsive-grid.js\";\n\ndescribe(\"responsive-grid test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"responsive-grid passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"responsive-grid passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"responsive-grid can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "responsive-utility": { - "element": "responsive-utility", - "component": { - "properties": [], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "responiveElementEvent", - "deleteResponiveElementEvent", - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../responsive-utility.js\";\n\ndescribe(\"responsive-utility test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"responsive-utility passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"responsive-utility passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"responsive-utility can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "MEDIUM" - }, - "retro-card": { - "element": "retro-card", - "component": { - "properties": [ - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "subtitle", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "tags", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "mediaSource", - "type": "String", - "attribute": "media-source", - "config": "type: String,\n attribute: \"media-source\"," - }, - { - "name": "url", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "hoverSource", - "type": "String", - "attribute": "hover-source", - "config": "type: String,\n attribute: \"hover-source\"," - }, - { - "name": "hoverState", - "type": "Boolean", - "attribute": "hover-state", - "config": "type: Boolean,\n attribute: \"hover-state\",\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "__cardTags", - "type": "Array", - "attribute": null, - "config": "type: Array,", - "defaultValue": "[]" - }, - { - "name": "nosource", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "__source", - "type": "String", - "attribute": null, - "config": "type: String," - } - ], - "slots": [], - "events": [], - "methods": [ - "switch", - "if", - "haxHooks", - "haxactiveElementChanged", - "haxeditModeChanged" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../retro-card.js\";\n\ndescribe(\"retro-card test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"retro-card passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"retro-card passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"retro-card can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, subtitle, tags, mediaSource, url, hoverSource, hoverState, __cardTags, nosource, __source" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "rich-text-editor": { - "element": "rich-text-editor", - "component": { - "properties": [ - { - "name": "id", - "type": "String", - "attribute": "id", - "config": "name: \"id\",\n type: String,\n reflect: true,\n attribute: \"id\",", - "defaultValue": "\"\"" - }, - { - "name": "contenteditable", - "type": "String", - "attribute": "contenteditable", - "config": "name: \"contenteditable\",\n type: String,\n reflect: true,\n attribute: \"contenteditable\"," - }, - { - "name": "disabled", - "type": "Boolean", - "attribute": "disabled", - "config": "name: \"disabled\",\n type: Boolean,\n attribute: \"disabled\",\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "disableHover", - "type": "Boolean", - "attribute": "disable-hover", - "config": "name: \"disableHover\",\n type: Boolean,\n attribute: \"disable-hover\"," - }, - { - "name": "placeholder", - "type": "String", - "attribute": "aria-placeholder", - "config": "name: \"placeholder\",\n type: String,\n reflect: false,\n attribute: \"aria-placeholder\",", - "defaultValue": "\"Click to edit\"" - }, - { - "name": "toolbarId", - "type": "String", - "attribute": "toolbar-id", - "config": "name: \"toolbarId\",\n type: String,\n reflect: true,\n attribute: \"toolbar-id\",", - "defaultValue": "\"\"" - }, - { - "name": "range", - "type": "Object", - "attribute": "range", - "config": "name: \"range\",\n type: Object,\n attribute: \"range\",", - "defaultValue": "undefined" - }, - { - "name": "rawhtml", - "type": "String", - "attribute": "rawhtml", - "config": "type: String,\n attribute: \"rawhtml\"," - }, - { - "name": "type", - "type": "String", - "attribute": "type", - "config": "name: \"type\",\n type: String,\n reflect: true,\n attribute: \"type\",", - "defaultValue": "\"rich-text-editor-toolbar\"" - }, - { - "name": "viewSource", - "type": "Boolean", - "attribute": "view-source", - "config": "type: Boolean,\n attribute: \"view-source\",\n reflect: true," - }, - { - "name": "__codeEditorValue", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "__needsUpdate", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean," - }, - { - "name": "__focused", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "__hovered", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - } - ], - "slots": [ - "default" - ], - "events": [ - "focus", - "contenteditable-change" - ], - "methods": [ - "if", - "focus", - "makeSticky" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "RichTextEditorBehaviors" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../rich-text-editor.js\";\n\ndescribe(\"rich-text-editor test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n

      \n I'm the easiest way to implement editable rich\n text.\n

      \n
      \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"rich-text-editor passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"rich-text-editor passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"rich-text-editor can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: id, contenteditable, disabled, disableHover, placeholder, toolbarId, range, rawhtml, type, viewSource, __codeEditorValue, __needsUpdate, __focused, __hovered" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: focus, contenteditable-change" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "rpg-character": { - "element": "rpg-character", - "component": { - "properties": [ - { - "name": "literalseed", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean", - "defaultValue": "false" - }, - { - "name": "accessories", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "height", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "142" - }, - { - "name": "width", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "113" - }, - { - "name": "base", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "face", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "faceItem", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "hair", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "pants", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "shirt", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "skin", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "hatColor", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "hat", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "\"none\"" - }, - { - "name": "walking", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "leg", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "\"\"" - }, - { - "name": "seed", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true", - "defaultValue": "null" - }, - { - "name": "speed", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "500" - }, - { - "name": "circle", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "fire", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "demo", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean", - "defaultValue": "false" - }, - { - "name": "reduceMotion", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean", - "defaultValue": "globalThis.matchMedia(\n \"(prefers-reduced-motion: reduce)\",\n ).matches" - } - ], - "slots": [], - "events": [], - "methods": [ - "if", - "randomColor", - "renderPiece", - "switch", - "for" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../rpg-character.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: literalseed, accessories, height, width, base, face, faceItem, hair, pants, shirt, skin, hatColor, hat, walking, leg, seed, speed, circle, fire, demo, reduceMotion" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "runkit-embed": { - "element": "runkit-embed", - "component": { - "properties": [ - { - "name": "nodeVersion", - "type": "String", - "attribute": "node-version", - "config": "type: String, attribute: \"node-version\"" - }, - { - "name": "mode", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "loading", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean", - "defaultValue": "false" - }, - { - "name": "source", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "dataHaxActive", - "type": "String", - "attribute": "data-hax-active", - "config": "type: String,\n reflect: true,\n attribute: \"data-hax-active\"," - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if", - "buildRunKit", - "haxHooks", - "haxeditModeChanged" - ], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": false, - "hasAccessibilityTests": false, - "testedProperties": [], - "testedSlots": [], - "testedEvents": [], - "testedMethods": [] - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "schema-behaviors": { - "element": "schema-behaviors", - "component": { - "properties": [ - { - "name": "schemaMap", - "type": "Object", - "attribute": null, - "config": "type: Object,\n readOnly: true,\n observer: \"_schemaMapChanged\"," - } - ], - "slots": [], - "events": [], - "methods": [ - "if", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../schema-behaviors.js\";\n\ndescribe(\"schema-behaviors test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"schema-behaviors passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"schema-behaviors passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"schema-behaviors can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: schemaMap" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "scroll-button": { - "element": "scroll-button", - "component": { - "properties": [ - { - "name": "target", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"icons:expand-less\"" - }, - { - "name": "label", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "_label", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "position", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "t", - "type": "Object", - "attribute": null, - "config": "type: Object," - } - ], - "slots": [], - "events": [ - "i18n-manager-register-element" - ], - "methods": [ - "if", - "scrollEvent" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../scroll-button.js\";\n\ndescribe(\"scroll-button test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"scroll-button passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"scroll-button passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"scroll-button can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: target, icon, label, _label, position, t" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "secure-request": { - "element": "secure-request", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "setCookies", - "generateUrl", - "if", - "getEndPoint", - "getCsrfToken", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../secure-request.js\";\n\ndescribe(\"secure-request test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"secure-request passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"secure-request passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"secure-request can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "self-check": { - "element": "self-check", - "component": { - "properties": [ - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"Self-Check\"" - }, - { - "name": "question", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "image", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "\"\"" - }, - { - "name": "alt", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "\"\"" - }, - { - "name": "describedBy", - "type": "String", - "attribute": "described-by", - "config": "type: String,\n attribute: \"described-by\"," - }, - { - "name": "link", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "correct", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - } - ], - "slots": [], - "events": [], - "methods": [ - "if", - "haxHooks", - "haxactiveElementChanged", - "openAnswer" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "I18NMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": true, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../self-check.js\";\n\ndescribe(\"self-check test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html`\n How large can the average great white shark grow to be?\n The Great White shark can grow to be 15 ft to more than 20 ft in length\n and weigh 2.5 tons or more.\n `,\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"self-check passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"self-check passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"self-check can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, question, image, alt, describedBy, link, correct" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "shadow-style": { - "element": "shadow-style", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../shadow-style.js\";\n\ndescribe(\"shadow-style test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"shadow-style passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"shadow-style passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"shadow-style can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "simple-autocomplete": { - "element": "simple-autocomplete", - "component": { - "properties": [ - { - "name": "opened", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "hideInput", - "type": "Boolean", - "attribute": "hide-input", - "config": "type: Boolean,\n attribute: \"hide-input\",", - "defaultValue": "false" - }, - { - "name": "selectionPosition", - "type": "Boolean", - "attribute": "selection-position", - "config": "type: Boolean,\n attribute: \"selection-position\",", - "defaultValue": "false" - }, - { - "name": "value", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "itemLimit", - "type": "Number", - "attribute": "item-limit", - "config": "type: Number,\n attribute: \"item-limit\",", - "defaultValue": "6" - } - ], - "slots": [], - "events": [], - "methods": [ - "setValue", - "if", - "processInput", - "inputChanged", - "a11yListKeys", - "switch", - "hardStopEvent", - "a11yInputKeys", - "getSelection", - "getRange", - "resetFocusOnInput", - "itemSelect", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SimpleFilterMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-autocomplete.js\";\n\ndescribe(\"simple-autocomplete test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-autocomplete passes accessibility test\", async () => {\n const el = await fixture(\n html` `\n );\n await expect(el).to.be.accessible();\n });\n it(\"simple-autocomplete passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-autocomplete can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: opened, hideInput, selectionPosition, value, itemLimit" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-blog": { - "element": "simple-blog", - "component": { - "properties": [ - { - "name": "selectedPage", - "type": "Number", - "attribute": "selected-page", - "config": "type: Number,\n reflect: true,\n attribute: \"selected-page\",", - "defaultValue": "0" - } - ], - "slots": [ - "default" - ], - "events": [ - "resize" - ], - "methods": [ - "if", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SimpleColorsSuper" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "// local development and mobx\nwindow.process = window.process || {\n env: {\n NODE_ENV: \"development\",\n },\n};\nimport { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-blog.js\";\n\ndescribe(\"simple-blog test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html` `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-blog passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-blog passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-blog can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: selectedPage" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: resize" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-colors": { - "element": "simple-colors", - "component": { - "properties": [ - { - "name": "accentColor", - "type": "String", - "attribute": "accent-color", - "config": "attribute: \"accent-color\",\n type: String,\n reflect: true,", - "defaultValue": "\"grey\"" - }, - { - "name": "dark", - "type": "Boolean", - "attribute": null, - "config": "name: \"dark\",\n type: Boolean,\n reflect: true,", - "defaultValue": "false" - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if", - "invertShade", - "getColorInfo", - "makeVariable", - "getContrastingColors", - "getContrastingShades", - "isContrastCompliant" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SimpleColorsSuper" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-colors.js\";\n\ndescribe(\"simple-colors test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-colors passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-colors passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-colors can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: accentColor, dark" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-colors-shared-styles": { - "element": "simple-colors-shared-styles", - "component": { - "properties": [ - { - "name": "colors", - "type": "Object", - "attribute": "colors", - "config": "attribute: \"colors\",\n type: Object," - }, - { - "name": "contrasts", - "type": "Object", - "attribute": "contrasts", - "config": "attribute: \"contrasts\",\n type: Object," - } - ], - "slots": [], - "events": [], - "methods": [ - "getColorInfo", - "makeVariable", - "getContrastingShades", - "getContrastingColors", - "isContrastCompliant", - "indexToShade", - "shadeToIndex", - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-colors-shared-styles.js\";\n\ndescribe(\"simple-colors-shared-styles test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-colors-shared-styles passes accessibility test\", async () => {\n const el = await fixture(\n html` `\n );\n await expect(el).to.be.accessible();\n });\n it(\"simple-colors-shared-styles passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-colors-shared-styles can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: colors, contrasts" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-cta": { - "element": "simple-cta", - "component": { - "properties": [ - { - "name": "link", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "label", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"icons:chevron-right\"" - }, - { - "name": "editMode", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean," - }, - { - "name": "hideIcon", - "type": "Boolean", - "attribute": "hide-icon", - "config": "type: Boolean,\n attribute: \"hide-icon\",", - "defaultValue": "false" - }, - { - "name": "large", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "null" - }, - { - "name": "light", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "null" - }, - { - "name": "hotline", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "null" - }, - { - "name": "saturate", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "null" - }, - { - "name": "disabled", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "haxHooks", - "haxeditModeChanged", - "if", - "haxactiveElementChanged" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "DDDPulseEffectSuper" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-cta.js\";\n\ndescribe(\"simple-cta test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-cta passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-cta passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-cta can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: link, label, icon, editMode, hideIcon, large, light, hotline, saturate, disabled" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-datetime": { - "element": "simple-datetime", - "component": { - "properties": [ - { - "name": "timestamp", - "type": "Number", - "attribute": null, - "config": "type: Number," - }, - { - "name": "format", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"M jS, Y\"" - }, - { - "name": "date", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "unix", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - } - ], - "slots": [], - "events": [], - "methods": [ - "formatDate", - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-datetime.js\";\n\ndescribe(\"simple-datetime test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html``,\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-datetime passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-datetime passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-datetime can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: timestamp, format, date, unix" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-emoji": { - "element": "simple-emoji", - "component": { - "properties": [], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-emoji.js\";\n\ndescribe(\"simple-emoji test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-emoji passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-emoji passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-emoji can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "MEDIUM" - }, - "simple-fields": { - "element": "simple-fields", - "component": { - "properties": [ - { - "name": "disableResponsive", - "type": "Boolean", - "attribute": "disable-responsive", - "config": "type: Boolean,\n reflect: true,\n attribute: \"disable-responsive\"," - }, - { - "name": "fields", - "type": "Array", - "attribute": null, - "config": "type: Array," - }, - { - "name": "schematizer", - "type": "Object", - "attribute": "schematizer", - "config": "type: Object,\n attribute: \"schematizer\"," - }, - { - "name": "label", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "__activeTabs", - "type": "Object", - "attribute": "active-path", - "config": "type: Object,\n attribute: \"active-path\"," - }, - { - "name": "codeTheme", - "type": "String", - "attribute": "code-theme", - "config": "type: String,\n attribute: \"code-theme\"," - } - ], - "slots": [ - "default" - ], - "events": [ - "active-tabs-changed" - ], - "methods": [ - "if", - "setActiveTab", - "setActivePath", - "fieldsToSchema" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-fields.js\";\n\ndescribe(\"simple-fields test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-fields passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-fields passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-fields can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: disableResponsive, fields, schematizer, label, __activeTabs, codeTheme" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: active-tabs-changed" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-filter": { - "element": "simple-filter", - "component": { - "properties": [ - { - "name": "items", - "type": "Array", - "attribute": null, - "config": "type: Array," - }, - { - "name": "like", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "where", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "caseSensitive", - "type": "Boolean", - "attribute": "case-sensitive", - "config": "type: Boolean,\n attribute: \"case-sensitive\",\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "multiMatch", - "type": "Boolean", - "attribute": "multi-match", - "config": "type: Boolean,\n attribute: \"multi-match\",", - "defaultValue": "false" - }, - { - "name": "filtered", - "type": "Array", - "attribute": null, - "config": "type: Array,", - "defaultValue": "[]" - } - ], - "slots": [], - "events": [ - "filter", - "filtered-changed" - ], - "methods": [ - "resetList", - "update", - "if", - "filter", - "escapeRegExp" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SimpleFilterMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-filter.js\";\n\ndescribe(\"simple-filter test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-filter passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-filter passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-filter can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: items, like, where, caseSensitive, multiMatch, filtered" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: filter, filtered-changed" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-icon": { - "element": "simple-icon", - "component": { - "properties": [ - { - "name": "contrast", - "type": "Number", - "attribute": "contrast", - "config": "type: Number,\n attribute: \"contrast\",\n reflect: true,", - "defaultValue": "0" - } - ], - "slots": [], - "events": [], - "methods": [], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [ - "SimpleIconBehaviors" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-icon.js\";\n\ndescribe(\"simple-icon test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-icon passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-icon passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-icon can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: contrast" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-icon-picker": { - "element": "simple-icon-picker", - "component": { - "properties": [ - { - "name": "allowNull", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "true" - }, - { - "name": "icons", - "type": "Array", - "attribute": null, - "config": "type: Array,", - "defaultValue": "[]" - }, - { - "name": "includeSets", - "type": "Array", - "attribute": "include-sets", - "config": "type: Array,\n attribute: \"include-sets\"," - }, - { - "name": "excludeSets", - "type": "Array", - "attribute": "exclude-sets", - "config": "type: Array,\n attribute: \"exclude-sets\"," - }, - { - "name": "exclude", - "type": "Array", - "attribute": "exclude", - "config": "type: Array,\n attribute: \"exclude\"," - }, - { - "name": "value", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "null" - }, - { - "name": "optionsPerRow", - "type": "Number", - "attribute": null, - "config": "type: Number,", - "defaultValue": "6" - }, - { - "name": "__iconList", - "type": "Array", - "attribute": null, - "config": "type: Array," - } - ], - "slots": [], - "events": [ - "value-changed" - ], - "methods": [ - "if", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-icon-picker.js\";\n\ndescribe(\"simple-icon-picker test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-icon-picker passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-icon-picker passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-icon-picker can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: allowNull, icons, includeSets, excludeSets, exclude, value, optionsPerRow, __iconList" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: value-changed" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-img": { - "element": "simple-img", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "updateconvertedurl", - "attributeChangedCallback" - ], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../simple-img.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "simple-login": { - "element": "simple-login", - "component": { - "properties": [ - { - "name": "username", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "password", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "loading", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "userInputLabel", - "type": "String", - "attribute": "user-input-label", - "config": "type: String,\n attribute: \"user-input-label\",", - "defaultValue": "\"User name\"" - }, - { - "name": "userInputErrMsg", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"User name required\"" - }, - { - "name": "passwordInputLabel", - "type": "String", - "attribute": "password-input-label", - "config": "type: String,\n attribute: \"password-input-label\",", - "defaultValue": "\"Password\"" - }, - { - "name": "passwordInputErrMsg", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"Password required\"" - }, - { - "name": "loginBtnText", - "type": "String", - "attribute": "login-btn-text", - "config": "type: String,\n attribute: \"login-btn-text\",", - "defaultValue": "\"Login\"" - } - ], - "slots": [], - "events": [ - "simple-login-login" - ], - "methods": [ - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-login.js\";\n\ndescribe(\"simple-login test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-login passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-login passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-login can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: username, password, loading, userInputLabel, userInputErrMsg, passwordInputLabel, passwordInputErrMsg, loginBtnText" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-login-login" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-modal": { - "element": "simple-modal", - "component": { - "properties": [ - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "opened", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "closeLabel", - "type": "String", - "attribute": "close-label", - "config": "attribute: \"close-label\",\n type: String,", - "defaultValue": "\"Close\"" - }, - { - "name": "closeIcon", - "type": "String", - "attribute": "close-icon", - "config": "type: String,\n attribute: \"close-icon\",", - "defaultValue": "\"close\"" - }, - { - "name": "invokedBy", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "modal", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "mode", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true," - } - ], - "slots": [], - "events": [ - "simple-toast-hide" - ], - "methods": [ - "if", - "showEvent", - "show", - "for", - "close", - "open", - "while" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-modal.js\";\n\ndescribe(\"simple-modal test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-modal passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-modal passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-modal can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, opened, closeLabel, closeIcon, invokedBy, modal, mode" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-toast-hide" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-picker": { - "element": "simple-picker", - "component": { - "properties": [ - { - "name": "allowNull", - "type": "Boolean", - "attribute": "allow-null", - "config": "type: Boolean,\n reflect: true,\n attribute: \"allow-null\",", - "defaultValue": "false" - }, - { - "name": "alignRight", - "type": "Boolean", - "attribute": "align-right", - "config": "type: Boolean,\n reflect: true,\n attribute: \"align-right\",", - "defaultValue": "false" - }, - { - "name": "ariaLabelledby", - "type": "String", - "attribute": "aria-labelledby", - "config": "type: String,\n attribute: \"aria-labelledby\",", - "defaultValue": "null" - }, - { - "name": "blockLabel", - "type": "Boolean", - "attribute": "block-label", - "config": "type: Boolean,\n reflect: true,\n attribute: \"block-label\",", - "defaultValue": "false" - }, - { - "name": "disabled", - "type": "Boolean", - "attribute": "disabled", - "config": "type: Boolean,\n reflect: true,\n attribute: \"disabled\",", - "defaultValue": "false" - }, - { - "name": "expanded", - "type": "Boolean", - "attribute": "expanded", - "config": "type: Boolean,\n reflect: true,\n attribute: \"expanded\",", - "defaultValue": "false" - }, - { - "name": "hideOptionLabels", - "type": "Boolean", - "attribute": "hide-option-labels", - "config": "type: Boolean,\n reflect: true,\n attribute: \"hide-option-labels\",", - "defaultValue": "false" - }, - { - "name": "hideNullOption", - "type": "Boolean", - "attribute": "hide-null-option", - "config": "type: Boolean,\n reflect: true,\n attribute: \"hide-null-option\"," - }, - { - "name": "hideSample", - "type": "Boolean", - "attribute": "hide-sample", - "config": "type: Boolean,\n reflect: true,\n attribute: \"hide-sample\",", - "defaultValue": "false" - }, - { - "name": "justify", - "type": "Boolean", - "attribute": "justify", - "config": "type: Boolean,\n reflect: true,\n attribute: \"justify\"," - }, - { - "name": "label", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "options", - "type": "Array", - "attribute": null, - "config": "type: Array,", - "defaultValue": "[]" - }, - { - "name": "titleAsHtml", - "type": "Boolean", - "attribute": "title-as-html", - "config": "type: Boolean,\n attribute: \"title-as-html\",", - "defaultValue": "false" - }, - { - "name": "value", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "null" - }, - { - "name": "__activeDesc", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"option-0-0\"" - }, - { - "name": "__options", - "type": "Array", - "attribute": null, - "config": "type: Array," - }, - { - "name": "__selectedOption", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "__ready", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - } - ], - "slots": [], - "events": [ - "changed", - "click", - "mousedown", - "keydown", - "option-focus", - "value-changed", - "change", - "expand", - "collapse" - ], - "methods": [ - "if", - "for", - "renderItem", - "setOptions" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SimplePickerBehaviors" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../lib/simple-emoji-picker.js\";\n\ndescribe(\"simple-picker test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-picker passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-picker passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-picker can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: allowNull, alignRight, ariaLabelledby, blockLabel, disabled, expanded, hideOptionLabels, hideNullOption, hideSample, justify, label, options, titleAsHtml, value, __activeDesc, __options, __selectedOption, __ready" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: changed, click, mousedown, keydown, option-focus, value-changed, change, expand, collapse" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-popover": { - "element": "simple-popover", - "component": { - "properties": [], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-popover.js\";\n/*\ndescribe(\"simple-popover test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html` `\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-popover passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-popover passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-popover can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "MEDIUM" - }, - "simple-progress": { - "element": "simple-progress", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "attributeChangedCallback" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-progress.js\";\n\ndescribe(\"simple-progress test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-progress passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-progress passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-progress can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "simple-range-input": { - "element": "simple-range-input", - "component": { - "properties": [ - { - "name": "dragging", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "immediateValue", - "type": "Number", - "attribute": "immediate-value", - "config": "type: Number, attribute: \"immediate-value\"", - "defaultValue": "0" - }, - { - "name": "value", - "type": "Number", - "attribute": null, - "config": "type: Number, reflect: true", - "defaultValue": "0" - }, - { - "name": "min", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - }, - { - "name": "step", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "1" - }, - { - "name": "max", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "100" - }, - { - "name": "label", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "\"Range input\"" - }, - { - "name": "disabled", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - } - ], - "slots": [], - "events": [ - "immediate-value-changed", - "value-changed" - ], - "methods": [ - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../simple-range-input.js\";\ndescribe(\"Image comparison\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: dragging, immediateValue, value, min, step, max, label, disabled" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: immediate-value-changed, value-changed" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: immediate-value" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-search": { - "element": "simple-search", - "component": { - "properties": [ - { - "name": "alwaysFloatLabel", - "type": "Boolean", - "attribute": "always-float-label", - "config": "attribute: \"always-float-label\",\n type: Boolean,", - "defaultValue": "false" - }, - { - "name": "caseSensitive", - "type": "Boolean", - "attribute": "case-sensitive", - "config": "attribute: \"case-sensitive\",\n type: Boolean,", - "defaultValue": "null" - }, - { - "name": "controls", - "type": "String", - "attribute": "controls", - "config": "attribute: \"controls\",\n type: String,", - "defaultValue": "null" - }, - { - "name": "inline", - "type": "Boolean", - "attribute": "inline", - "config": "attribute: \"inline\",\n type: Boolean," - }, - { - "name": "nextButtonIcon", - "type": "String", - "attribute": "next-button-icon", - "config": "attribute: \"next-button-icon\",\n type: String,", - "defaultValue": "\"arrow-forward\"" - }, - { - "name": "nextButtonLabel", - "type": "String", - "attribute": "next-button-label", - "config": "attribute: \"next-button-label\",\n type: String,", - "defaultValue": "\"next result\"" - }, - { - "name": "noLabelFloat", - "type": "Boolean", - "attribute": "no-label-float", - "config": "attribute: \"no-label-float\",\n type: Boolean,", - "defaultValue": "false" - }, - { - "name": "prevButtonIcon", - "type": "String", - "attribute": "prev-button-icon", - "config": "attribute: \"prev-button-icon\",\n type: String,", - "defaultValue": "\"arrow-back\"" - }, - { - "name": "prevButtonLabel", - "type": "String", - "attribute": "prev-button-label", - "config": "attribute: \"prev-button-label\",\n type: String,", - "defaultValue": "\"previous result\"" - }, - { - "name": "resultCount", - "type": "Number", - "attribute": "result-count", - "config": "attribute: \"result-count\",\n type: Number,", - "defaultValue": "0" - }, - { - "name": "resultPointer", - "type": "Number", - "attribute": "result-pointer", - "config": "attribute: \"result-pointer\",\n type: Number,", - "defaultValue": "0" - }, - { - "name": "selector", - "type": "String", - "attribute": "selector", - "config": "attribute: \"selector\",\n type: String,", - "defaultValue": "null" - }, - { - "name": "searchInputIcon", - "type": "String", - "attribute": "search-input-icon", - "config": "attribute: \"search-input-icon\",\n type: String,", - "defaultValue": "\"search\"" - }, - { - "name": "searchInputLabel", - "type": "String", - "attribute": "search-input-label", - "config": "attribute: \"search-input-label\",\n type: String,", - "defaultValue": "\"search\"" - }, - { - "name": "searchTerms", - "type": "Array", - "attribute": "search-terms", - "config": "attribute: \"search-terms\",\n type: Array,", - "defaultValue": "[]" - }, - { - "name": "target", - "type": "Object", - "attribute": null, - "config": "type: Object,", - "defaultValue": "null" - }, - { - "name": "__hideNext", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "true" - }, - { - "name": "__hidePrev", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "true" - } - ], - "slots": [], - "events": [ - "simple-search", - "goto-result" - ], - "methods": [ - "if", - "for", - "findMatches" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-search.js\";\n\ndescribe(\"simple-search test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-search passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-search passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-search can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alwaysFloatLabel, caseSensitive, controls, inline, nextButtonIcon, nextButtonLabel, noLabelFloat, prevButtonIcon, prevButtonLabel, resultCount, resultPointer, selector, searchInputIcon, searchInputLabel, searchTerms, target, __hideNext, __hidePrev" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-search, goto-result" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-toast": { - "element": "simple-toast", - "component": { - "properties": [ - { - "name": "opened", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true," - }, - { - "name": "text", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "classStyle", - "type": "String", - "attribute": "class-style", - "config": "type: String,\n attribute: \"class-style\"," - }, - { - "name": "closeText", - "type": "String", - "attribute": "close-text", - "config": "type: String,\n attribute: \"close-text\"," - }, - { - "name": "duration", - "type": "Number", - "attribute": null, - "config": "type: Number," - }, - { - "name": "eventCallback", - "type": "String", - "attribute": "event-callback", - "config": "type: String,\n attribute: \"event-callback\"," - }, - { - "name": "closeButton", - "type": "Boolean", - "attribute": "close-button", - "config": "type: Boolean,\n reflect: true,\n attribute: \"close-button\"," - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if", - "hideSimpleToast", - "openedChanged", - "setDefaultToast", - "while", - "showSimpleToast", - "show", - "hide" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-toast.js\";\n\ndescribe(\"simple-toast test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-toast passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-toast passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-toast can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: opened, text, classStyle, closeText, duration, eventCallback, closeButton" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-toolbar": { - "element": "simple-toolbar", - "component": { - "properties": [ - { - "name": "alwaysExpanded", - "type": "Boolean", - "attribute": "always-expanded", - "config": "name: \"alwaysExpanded\",\n type: Boolean,\n attribute: \"always-expanded\",\n reflect: true," - }, - { - "name": "ariaControls", - "type": "String", - "attribute": "aria-controls", - "config": "name: \"ariaControls\",\n type: String,\n attribute: \"aria-controls\",\n reflect: true," - }, - { - "name": "ariaLabel", - "type": "String", - "attribute": "aria-label", - "config": "name: \"ariaLabel\",\n type: String,\n attribute: \"aria-label\",\n reflect: true," - }, - { - "name": "collapsed", - "type": "Boolean", - "attribute": "collapsed", - "config": "name: \"collapsed\",\n type: Boolean,\n attribute: \"collapsed\",\n reflect: true,", - "defaultValue": "true" - }, - { - "name": "config", - "type": "Array", - "attribute": "config", - "config": "name: \"config\",\n type: Array,\n attribute: \"config\",", - "defaultValue": "[]" - }, - { - "name": "id", - "type": "String", - "attribute": "id", - "config": "name: \"id\",\n type: String,\n attribute: \"id\",\n reflect: true," - }, - { - "name": "moreShortcuts", - "type": "Object", - "attribute": "more-shortcuts", - "config": "name: \"moreShortcuts\",\n attribute: \"more-shortcuts\",\n type: Object," - }, - { - "name": "shortcutKeys", - "type": "Object", - "attribute": "shortcut-keys", - "config": "name: \"shortcutKeys\",\n attribute: \"shortcut-keys\",\n type: Object," - }, - { - "name": "sticky", - "type": "Boolean", - "attribute": "sticky", - "config": "name: \"sticky\",\n type: Boolean,\n attribute: \"sticky\",\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "__buttons", - "type": "Array", - "attribute": null, - "config": "name: \"__buttons\",\n type: Array,", - "defaultValue": "[]" - }, - { - "name": "collapseDisabled", - "type": "Boolean", - "attribute": "collapse-disabled", - "config": "type: Boolean,\n attribute: \"collapse-disabled\",\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "__focused", - "type": "Boolean", - "attribute": null, - "config": "name: \"__focused\",\n type: Boolean,", - "defaultValue": "false" - }, - { - "name": "__hovered", - "type": "Boolean", - "attribute": null, - "config": "name: \"__hovered\",\n type: Boolean,", - "defaultValue": "false" - } - ], - "slots": [], - "events": [ - "shortcut-key-pressed" - ], - "methods": [ - "if", - "addButton", - "addButtonGroup", - "clearToolbar", - "deregisterButton", - "registerButton", - "resizeToolbar", - "getItemIndex", - "getItem", - "setCurrentItem", - "focusOn", - "updateButton", - "updateToolbar", - "switch", - "while" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "SimpleToolbarBehaviors" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-toolbar.js\";\n\ndescribe(\"simple-toolbar test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-toolbar passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-toolbar passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-toolbar can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alwaysExpanded, ariaControls, ariaLabel, collapsed, config, id, moreShortcuts, shortcutKeys, sticky, __buttons, collapseDisabled, __focused, __hovered" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: shortcut-key-pressed" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-tooltip": { - "element": "simple-tooltip", - "component": { - "properties": [ - { - "name": "for", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "manualMode", - "type": "Boolean", - "attribute": "manual-mode", - "config": "type: Boolean, attribute: \"manual-mode\"", - "defaultValue": "false" - }, - { - "name": "position", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "\"bottom\"" - }, - { - "name": "fitToVisibleBounds", - "type": "Boolean", - "attribute": "fit-to-visible-bounds", - "config": "type: Boolean,\n attribute: \"fit-to-visible-bounds\",", - "defaultValue": "false" - }, - { - "name": "offset", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "14" - }, - { - "name": "marginTop", - "type": "Number", - "attribute": "margin-top", - "config": "type: Number, attribute: \"margin-top\"", - "defaultValue": "14" - }, - { - "name": "animationDelay", - "type": "Number", - "attribute": "animation-delay", - "config": "type: Number, attribute: \"animation-delay\"" - }, - { - "name": "animationEntry", - "type": "String", - "attribute": "animation-entry", - "config": "type: String, attribute: \"animation-entry\"", - "defaultValue": "\"\"" - }, - { - "name": "animationExit", - "type": "String", - "attribute": "animation-exit", - "config": "type: String, attribute: \"animation-exit\"", - "defaultValue": "\"\"" - }, - { - "name": "_showing", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean" - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if", - "playAnimation", - "cancelAnimation", - "show", - "for", - "hide", - "updatePosition", - "switch" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-tooltip.js\";\n\ndescribe(\"simple-tooltip test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-tooltip passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-tooltip passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-tooltip can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: for, manualMode, position, fitToVisibleBounds, offset, marginTop, animationDelay, animationEntry, animationExit, _showing" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "simple-wc": { - "element": "simple-wc", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "for", - "if", - "haxProperties" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../simple-wc.js\";\n\ndescribe(\"simple-wc test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html` `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"simple-wc passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"simple-wc passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"simple-wc can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "social-share-link": { - "element": "social-share-link", - "component": { - "properties": [ - { - "name": "buttonStyle", - "type": "Boolean", - "attribute": "button-style", - "config": "type: Boolean,\n reflect: true,\n attribute: \"button-style\",", - "defaultValue": "false" - }, - { - "name": "image", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "message", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"\"" - }, - { - "name": "mode", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "text", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "type", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"Twitter\"" - }, - { - "name": "url", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "__href", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "__icon", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "__linkText", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "__showIcon", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean," - } - ], - "slots": [], - "events": [], - "methods": [ - "if", - "switch" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../social-share-link.js\";\n\ndescribe(\"social-share-link test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"social-share-link passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"social-share-link passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"social-share-link can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: buttonStyle, image, message, mode, text, type, url, __href, __icon, __linkText, __showIcon" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "sorting-question": { - "element": "sorting-question", - "component": { - "properties": [ - { - "name": "numberCorrect", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - } - ], - "slots": [], - "events": [ - "simple-toast-hide", - "user-engagement" - ], - "methods": [ - "getOptions", - "if", - "checkAnswerCallback", - "resetAnswer", - "for", - "renderInteraction", - "inactiveCase", - "renderDirections", - "renderFeedback", - "haxinlineContextMenu", - "haxClickInlineAdd", - "haxClickInlineRemove" - ], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../sorting-question.js\";\n\ndescribe(\"sorting-question test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"sorting-question passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"sorting-question passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"sorting-question can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: numberCorrect" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-toast-hide, user-engagement" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "spotify-embed": { - "element": "spotify-embed", - "component": { - "properties": [ - { - "name": "source", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "null" - }, - { - "name": "theme", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "null" - }, - { - "name": "size", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "\"normal\"" - }, - { - "name": "playlistid", - "type": "String", - "attribute": null, - "config": "type: String, attribute: false", - "defaultValue": "null" - }, - { - "name": "type", - "type": "String", - "attribute": null, - "config": "type: String, attribute: false", - "defaultValue": "null" - }, - { - "name": "editing", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - } - ], - "slots": [], - "events": [], - "methods": [ - "if", - "haxHooks", - "haxactiveElementChanged", - "haxeditModeChanged" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../spotify-embed.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, theme, size, playlistid, type, editing" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "star-rating": { - "element": "star-rating", - "component": { - "properties": [ - { - "name": "score", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "10" - }, - { - "name": "possible", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "100" - }, - { - "name": "interactive", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true" - }, - { - "name": "numStars", - "type": "Number", - "attribute": "num-stars", - "config": "type: Number, attribute: \"num-stars\"", - "defaultValue": "5" - }, - { - "name": "_calPercent", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - } - ], - "slots": [], - "events": [ - "star-rating-click" - ], - "methods": [ - "renderStar", - "if", - "while", - "interactiveEvent" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../star-rating.js\";\n\ndescribe(\"star-rating test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"star-rating passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"star-rating passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"star-rating can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: score, possible, interactive, numStars, _calPercent" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: star-rating-click" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "stop-note": { - "element": "stop-note", - "component": { - "properties": [ - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "\"\"" - }, - { - "name": "url", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "\"stopnoteicons:stop-icon\"" - }, - { - "name": "status", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "null" - } - ], - "slots": [ - "default", - "message" - ], - "events": [], - "methods": [ - "if", - "haxHooks", - "haxeditModeChanged", - "haxactiveElementChanged" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "I18NMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": true, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../stop-note.js\";\n\ndescribe(\"stop-note test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html`\n You can write any confirmation\n `,\n );\n });\n\n it(\"message slot correct\", async () => {\n expect(\n element.shadowRoot\n .querySelector(\"slot[name='message']\")\n .assignedNodes({ flatten: true })[0].textContent,\n ).to.equal(\"You can write any confirmation\");\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, url, icon, status" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "super-daemon": { - "element": "super-daemon", - "component": { - "properties": [ - { - "name": "santaMode", - "type": "Boolean", - "attribute": "santa-mode", - "config": "type: Boolean, reflect: true, attribute: \"santa-mode\"" - }, - { - "name": "opened", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true" - }, - { - "name": "loading", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true" - }, - { - "name": "key1", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "key2", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "icon", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "items", - "type": "Array", - "attribute": null, - "config": "type: Array" - }, - { - "name": "programResults", - "type": "Array", - "attribute": null, - "config": "type: Array" - }, - { - "name": "programName", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "allItems", - "type": "Array", - "attribute": null, - "config": "type: Array" - }, - { - "name": "context", - "type": "Array", - "attribute": null, - "config": "type: Array" - }, - { - "name": "commandContext", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "program", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "programSearch", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "like", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "value", - "type": "String", - "attribute": null, - "config": "type: String" - }, - { - "name": "mini", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean" - }, - { - "name": "wand", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true" - }, - { - "name": "activeNode", - "type": "Object", - "attribute": null, - "config": "type: Object" - }, - { - "name": "programTarget", - "type": "Object", - "attribute": null, - "config": "type: Object" - }, - { - "name": "voiceSearch", - "type": "Boolean", - "attribute": "voice-search", - "config": "type: Boolean, reflect: true, attribute: \"voice-search\"" - }, - { - "name": "voiceCommands", - "type": "Object", - "attribute": null, - "config": "type: Object" - }, - { - "name": "listeningForInput", - "type": "Boolean", - "attribute": "listening-for-input", - "config": "type: Boolean,\n reflect: true,\n attribute: \"listening-for-input\"," - } - ], - "slots": [], - "events": [ - "super-daemon-close", - "super-daemon-toast-hide", - "super-daemon-command-context-changed", - "super-daemon-context-changed" - ], - "methods": [ - "if", - "waveWand", - "runProgramEvent", - "elementMethod", - "elementClick", - "defineOptionEvent", - "defineOption", - "updateSearchInputViaVoice", - "addVoiceCommand", - "keyHandler", - "setListeningStatus", - "close", - "filterItems", - "playSound", - "appendContext", - "removeContext", - "clickOnMiniMode", - "miniCancel", - "open", - "focusout", - "while", - "noResultsSlot", - "likeChanged", - "randomResponse", - "toggleSantaMode", - "merlinSpeak", - "promptMerlin", - "stopMerlin", - "closeMerlin", - "belsnickel", - "defaultVoiceCommands", - "itemsForDisplay", - "reprocessVoiceCommands", - "commandContextChanged", - "switch", - "keyHandlerCallback", - "allowedCallback" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../super-daemon.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: santaMode, opened, loading, key1, key2, icon, items, programResults, programName, allItems, context, commandContext, program, programSearch, like, value, mini, wand, activeNode, programTarget, voiceSearch, voiceCommands, listeningForInput" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: super-daemon-close, super-daemon-toast-hide, super-daemon-command-context-changed, super-daemon-context-changed" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: santa-mode, voice-search, listening-for-input" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "tagging-question": { - "element": "tagging-question", - "component": { - "properties": [ - { - "name": "dragging", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean, reflect: true", - "defaultValue": "false" - }, - { - "name": "dragEnter", - "type": "Boolean", - "attribute": "drag-enter", - "config": "type: Boolean, reflect: true, attribute: \"drag-enter\"", - "defaultValue": "false" - }, - { - "name": "dragEnterAnswer", - "type": "Boolean", - "attribute": "drag-enter-answer", - "config": "type: Boolean,\n reflect: true,\n attribute: \"drag-enter-answer\",", - "defaultValue": "false" - }, - { - "name": "selectedAnswers", - "type": "Array", - "attribute": null, - "config": "type: Array", - "defaultValue": "[]" - } - ], - "slots": [], - "events": [ - "simple-toast-hide" - ], - "methods": [ - "renderDirections", - "renderInteraction", - "isCorrect", - "renderFeedback", - "randomizeOptions", - "for", - "handleDrag", - "handleDragEnd", - "allowDrop", - "allowDropAnswer", - "handleDrop", - "handleTagMove", - "if", - "handleTagClick", - "addTag", - "removeTag", - "resetAnswer" - ], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": false, - "hasAccessibilityTests": false, - "testedProperties": [], - "testedSlots": [], - "testedEvents": [], - "testedMethods": [] - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "terrible-themes": { - "element": "terrible-themes", - "component": { - "properties": [], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if", - "for" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "HAXCMSRememberRoute" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../terrible-themes.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n /*\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });*/\n});\n" - }, - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "MEDIUM" - }, - "training-theme": { - "element": "training-theme", - "component": { - "properties": [ - { - "name": "items", - "type": "Array", - "attribute": null, - "config": "type: Array", - "defaultValue": "[]" - }, - { - "name": "activeId", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "null" - }, - { - "name": "time", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "0" - }, - { - "name": "prevPage", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "nextPage", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "maxIndex", - "type": "Number", - "attribute": null, - "config": "type: Number", - "defaultValue": "0" - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "HAXCMSOperationButtons" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../training-theme.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: items, activeId, time, prevPage, nextPage, maxIndex" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "twitter-embed": { - "element": "twitter-embed", - "component": { - "properties": [ - { - "name": "tweet", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "null" - }, - { - "name": "_haxstate", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean," - }, - { - "name": "lang", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "globalThis.document.body.getAttribute(\"xml:lang\") ||\n globalThis.document.body.getAttribute(\"lang\") ||\n globalThis.document.documentElement.getAttribute(\"xml:lang\") ||\n globalThis.document.documentElement.getAttribute(\"lang\") ||\n globalThis.navigator.language ||\n FALLBACK_LANG" - }, - { - "name": "dataWidth", - "type": "String", - "attribute": "data-width", - "config": "type: String,\n attribute: \"data-width\",", - "defaultValue": "\"550px\"" - }, - { - "name": "dataTheme", - "type": "String", - "attribute": "data-theme", - "config": "type: String,\n attribute: \"data-theme\",", - "defaultValue": "\"light\"" - }, - { - "name": "tweetId", - "type": "String", - "attribute": "tweet-id", - "config": "type: String,\n attribute: \"tweet-id\",", - "defaultValue": "null" - }, - { - "name": "noPopups", - "type": "Boolean", - "attribute": "no-popups", - "config": "type: Boolean,\n attribute: \"no-popups\"," - }, - { - "name": "allowPopups", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"allow-popups\"" - } - ], - "slots": [], - "events": [ - "i18n-manager-register-element" - ], - "methods": [ - "haxHooks", - "haxgizmoRegistration", - "haxactiveElementChanged", - "if", - "haxeditModeChanged" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../twitter-embed.js\";\n\ndescribe(\"twitter-embed test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"twitter-embed passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"twitter-embed passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"twitter-embed can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: tweet, _haxstate, lang, dataWidth, dataTheme, tweetId, noPopups, allowPopups" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "type-writer": { - "element": "type-writer", - "component": { - "properties": [ - { - "name": "delay", - "type": "Number", - "attribute": null, - "config": "type: Number,", - "defaultValue": "100" - }, - { - "name": "cursorDuration", - "type": "Number", - "attribute": "cursor-duration", - "config": "type: Number,\n attribute: \"cursor-duration\",", - "defaultValue": "0" - }, - { - "name": "text", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "speed", - "type": "Number", - "attribute": null, - "config": "type: Number,", - "defaultValue": "150" - }, - { - "name": "elementVisible", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean," - }, - { - "name": "eraseSpeed", - "type": "Number", - "attribute": "erase-speed", - "config": "type: Number,\n attribute: \"erase-speed\",", - "defaultValue": "80" - }, - { - "name": "typing", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "_length", - "type": "Number", - "attribute": null, - "config": "type: Number," - }, - { - "name": "_oldText", - "type": "String", - "attribute": null, - "config": "type: String," - } - ], - "slots": [], - "events": [ - "type-writer-end" - ], - "methods": [ - "if", - "type", - "erase" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "IntersectionObserverMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../type-writer.js\";\n\ndescribe(\"type-writer test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"type-writer passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"type-writer passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"type-writer can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: delay, cursorDuration, text, speed, elementVisible, eraseSpeed, typing, _length, _oldText" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: type-writer-end" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "un-sdg": { - "element": "un-sdg", - "component": { - "properties": [ - { - "name": "loading", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "'lazy'" - }, - { - "name": "fetchpriority", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "'low'" - }, - { - "name": "goal", - "type": "Number", - "attribute": null, - "config": "type: Number, reflect: true", - "defaultValue": "1" - }, - { - "name": "colorOnly", - "type": "Boolean", - "attribute": "color-only", - "config": "type: Boolean, attribute: 'color-only'", - "defaultValue": "false" - }, - { - "name": "alt", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "''" - } - ], - "slots": [], - "events": [], - "methods": [ - "if" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { html, fixture, expect } from '@open-wc/testing';\nimport {unSDGGoalData} from \"../un-sdg.js\";\n\nconst goalnum = 4;\ndescribe(\"UN SDG tests\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"Number for goal\", async () => {\n expect(element.goal).to.equal(goalnum);\n });\n it(\"Number for goal\", async () => {\n expect(element.alt).to.equal(`Goal ${goalnum}: ${unSDGGoalData[goalnum-1].name}`);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: loading, fetchpriority, goal, colorOnly, alt" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: color-only" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "undo-manager": { - "element": "undo-manager", - "component": { - "properties": [ - { - "name": "canUndo", - "type": "Boolean", - "attribute": "can-undo", - "config": "type: Boolean,\n attribute: \"can-undo\"," - }, - { - "name": "canRedo", - "type": "Boolean", - "attribute": "can-redo", - "config": "type: Boolean,\n attribute: \"can-redo\"," - }, - { - "name": "undoStackObserverProps", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "target", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "stack", - "type": "Object", - "attribute": null, - "config": "type: Object," - } - ], - "slots": [ - "default" - ], - "events": [ - "stack-changed", - "can-undo-changed", - "can-redo-changed" - ], - "methods": [ - "if", - "undoManagerStackLogic", - "undo", - "redo", - "commands", - "execute", - "canUndo", - "canRedo", - "changed" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "UndoManagerBehaviors" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../undo-manager.js\";\n\ndescribe(\"undo-manager test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"undo-manager passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"undo-manager passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"undo-manager can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: canUndo, canRedo, undoStackObserverProps, target, stack" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: stack-changed, can-undo-changed, can-redo-changed" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "unity-webgl": { - "element": "unity-webgl", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "attributeChangedCallback" - ], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n/*\nimport \"../unity-webgl.js\";\n\ndescribe(\"unity-webgl test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html`\n `\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n*/\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"unity-webgl passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"unity-webgl passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"unity-webgl can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "user-action": { - "element": "user-action", - "component": { - "properties": [], - "slots": [], - "events": [ - "i18n-manager-register-element" - ], - "methods": [ - "if", - "attributeChangedCallback", - "switch", - "handleIntersectionCallback", - "for", - "userActionEvent", - "haxHooks", - "haxgizmoRegistration", - "haxactiveElementChanged", - "haxeditModeChanged" - ], - "hasHaxProperties": true, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../user-action.js\";\n\ndescribe(\"user-action test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"user-action passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"user-action passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"user-action can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - } - ], - "needsWork": true, - "priority": "MEDIUM" - }, - "user-scaffold": { - "element": "user-scaffold", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "userActionArchitypes", - "userKeyDownAction", - "userPasteAction", - "userDropAction", - "userDragAction", - "userMouseAction", - "incrementWriteMemory", - "writeMemory", - "deleteMemory", - "readMemory", - "isBase64" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\nimport \"../user-scaffold.js\";\n\ndescribe(\"elementName test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html``);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "utils": { - "element": "utils", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "for", - "while", - "switch", - "function" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": false, - "hasAccessibilityTests": false, - "testedProperties": [], - "testedSlots": [], - "testedEvents": [], - "testedMethods": [] - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "video-player": { - "element": "video-player", - "component": { - "properties": [ - { - "name": "sourceType", - "type": "String", - "attribute": null, - "config": "type: String", - "defaultValue": "\"\"" - }, - { - "name": "accentColor", - "type": "String", - "attribute": "accent-color", - "config": "type: String,\n attribute: \"accent-color\",\n reflect: true," - }, - { - "name": "crossorigin", - "type": "String", - "attribute": "crossorigin", - "config": "type: String,\n attribute: \"crossorigin\",\n reflect: true,", - "defaultValue": "\"anonymous\"" - }, - { - "name": "dark", - "type": "Boolean", - "attribute": "dark", - "config": "type: Boolean,\n attribute: \"dark\",\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "darkTranscript", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "disableInteractive", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "height", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "hideTimestamps", - "type": "Boolean", - "attribute": "hide-timestamps", - "config": "type: Boolean,\n attribute: \"hide-timestamps\",", - "defaultValue": "false" - }, - { - "name": "hideTranscript", - "type": "Boolean", - "attribute": "hide-transcript", - "config": "type: Boolean,\n reflect: true,\n attribute: \"hide-transcript\",", - "defaultValue": "false" - }, - { - "name": "id", - "type": "String", - "attribute": "id", - "config": "type: String,\n attribute: \"id\",\n reflect: true," - }, - { - "name": "learningMode", - "type": "Boolean", - "attribute": "learning-mode", - "config": "type: Boolean,\n attribute: \"learning-mode\",", - "defaultValue": "false" - }, - { - "name": "lang", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"en\"" - }, - { - "name": "linkable", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "mediaTitle", - "type": "String", - "attribute": "media-title", - "config": "type: String,\n attribute: \"media-title\",\n reflect: true," - }, - { - "name": "hideYoutubeLink", - "type": "Boolean", - "attribute": "hide-youtube-link", - "config": "type: Boolean,\n attribute: \"hide-youtube-link\",", - "defaultValue": "false" - }, - { - "name": "source", - "type": "String", - "attribute": null, - "config": "type: String,\n reflect: true,", - "defaultValue": "\"\"" - }, - { - "name": "sources", - "type": "Array", - "attribute": null, - "config": "type: Array,", - "defaultValue": "[]" - }, - { - "name": "sourceData", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "stickyCorner", - "type": "String", - "attribute": "sticky-corner", - "config": "type: String,\n attribute: \"sticky-corner\",\n reflect: true,", - "defaultValue": "\"none\"" - }, - { - "name": "track", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "tracks", - "type": "Array", - "attribute": null, - "config": "type: Array,", - "defaultValue": "[]" - }, - { - "name": "thumbnailSrc", - "type": "String", - "attribute": "thumbnail-src", - "config": "type: String,\n attribute: \"thumbnail-src\",\n reflect: true," - }, - { - "name": "width", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "playing", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,\n reflect: true,", - "defaultValue": "false" - }, - { - "name": "allowBackgroundPlay", - "type": "Boolean", - "attribute": "allow-background-play", - "config": "type: Boolean,\n reflect: true,\n attribute: \"allow-background-play\",", - "defaultValue": "false" - }, - { - "name": "startTime", - "type": "Number", - "attribute": "start-time", - "config": "type: Number,\n attribute: \"start-time\",", - "defaultValue": "null" - }, - { - "name": "endTime", - "type": "Number", - "attribute": "end-time", - "config": "type: Number,\n attribute: \"end-time\",", - "defaultValue": "null" - } - ], - "slots": [], - "events": [], - "methods": [ - "querySelectorAll", - "if", - "haxHooks", - "haxinlineContextMenu", - "haxClickTimeCode", - "haxpostProcessNodeToContent", - "setSourceData", - "playEvent", - "pauseEvent", - "restart", - "restartEvent", - "pause", - "play", - "seek", - "endTimeTest" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "IntersectionObserverMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../video-player.js\";\n\ndescribe(\"video-player test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n \n \n \n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"video-player passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"video-player passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"video-player can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: sourceType, accentColor, crossorigin, dark, darkTranscript, disableInteractive, height, hideTimestamps, hideTranscript, id, learningMode, lang, linkable, mediaTitle, hideYoutubeLink, source, sources, sourceData, stickyCorner, track, tracks, thumbnailSrc, width, playing, allowBackgroundPlay, startTime, endTime" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "vocab-term": { - "element": "vocab-term", - "component": { - "properties": [ - { - "name": "popoverMode", - "type": "Boolean", - "attribute": "popover-mode", - "config": "type: Boolean, reflect: true, attribute: \"popover-mode\"", - "defaultValue": "false" - }, - { - "name": "detailsOpen", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean", - "defaultValue": "false" - } - ], - "slots": [], - "events": [], - "methods": [ - "haxHooks", - "haxactiveElementChanged", - "if", - "haxeditModeChanged", - "toggleOpen", - "detailsFocusOut" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../vocab-term.js\";\n\ndescribe(\"vocab-term test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"vocab-term passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"vocab-term passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"vocab-term can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: popoverMode, detailsOpen" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "voice-recorder": { - "element": "voice-recorder", - "component": { - "properties": [ - { - "name": "recording", - "type": "Boolean", - "attribute": null, - "config": "type: Boolean,", - "defaultValue": "false" - }, - { - "name": "label", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "\"Activate Recorder\"" - } - ], - "slots": [], - "events": [ - "voice-recorder-recording-blob" - ], - "methods": [ - "toggleRecording", - "recorder" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../voice-recorder.js\";\n\ndescribe(\"voice-recorder test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"voice-recorder passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"voice-recorder passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"voice-recorder can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: recording, label" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: voice-recorder-recording-blob" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "wc-autoload": { - "element": "wc-autoload", - "component": { - "properties": [], - "slots": [], - "events": [], - "methods": [ - "if", - "for", - "processNewElement" - ], - "hasHaxProperties": false, - "extendsLitElement": false, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../wc-autoload.js\";\n\ndescribe(\"wc-autoload test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"wc-autoload passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"wc-autoload passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"wc-autoload can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [], - "needsWork": false, - "priority": "LOW" - }, - "web-container": { - "element": "web-container", - "component": { - "properties": [ - { - "name": "fname", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true", - "defaultValue": "null" - }, - { - "name": "status", - "type": "String", - "attribute": null, - "config": "type: String, reflect: true", - "defaultValue": "\"Loading\"" - }, - { - "name": "files", - "type": "Object", - "attribute": null, - "config": "type: Object" - }, - { - "name": "filesShown", - "type": "Array", - "attribute": null, - "config": "type: Array", - "defaultValue": "[]" - }, - { - "name": "hideTerminal", - "type": "Boolean", - "attribute": "hide-terminal", - "config": "type: Boolean, reflect: true, attribute: 'hide-terminal'", - "defaultValue": "false" - }, - { - "name": "hideEditor", - "type": "Boolean", - "attribute": "hide-editor", - "config": "type: Boolean, reflect: true, attribute: 'hide-editor'", - "defaultValue": "false" - }, - { - "name": "hideWindow", - "type": "Boolean", - "attribute": "hide-window", - "config": "type: Boolean, reflect: true, attribute: 'hide-window'", - "defaultValue": "false" - } - ], - "slots": [], - "events": [ - "web-container-dependencies-installing", - "web-container-dependencies-installed", - "web-container-npm-start", - "web-container-server-ready", - "web-container-command-start", - "web-container-command-finished" - ], - "methods": [ - "write", - "if", - "for", - "getLanguageFromFileEnding", - "setCodeEditor", - "editorValueChanged", - "refreshIframe" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "DDDSuper" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": false, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { html, fixture, expect } from '@open-wc/testing';\nimport \"../web-container.js\";\n\ndescribe(\"webContainer test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"basic will it blend\", async () => {\n expect(element).to.exist;\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: fname, status, files, filesShown, hideTerminal, hideEditor, hideWindow" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: web-container-dependencies-installing, web-container-dependencies-installed, web-container-npm-start, web-container-server-ready, web-container-command-start, web-container-command-finished" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: hide-terminal, hide-editor, hide-window" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "wikipedia-query": { - "element": "wikipedia-query", - "component": { - "properties": [ - { - "name": "title", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "__now", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "_title", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "headers", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "hideTitle", - "type": "Boolean", - "attribute": "hide-title", - "config": "type: Boolean,\n attribute: \"hide-title\",", - "defaultValue": "false" - }, - { - "name": "search", - "type": "String", - "attribute": null, - "config": "type: String," - }, - { - "name": "language", - "type": "String", - "attribute": null, - "config": "type: String,", - "defaultValue": "language.split(\"-\")[0]" - } - ], - "slots": [], - "events": [ - "i18n-manager-register-element", - "hax-register-app" - ], - "methods": [ - "updateArticle", - "if", - "handleResponse", - "for", - "haxHooks", - "haxgizmoRegistration" - ], - "hasHaxProperties": true, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "IntersectionObserverMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../wikipedia-query.js\";\n\ndescribe(\"wikipedia-query test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"wikipedia-query passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"wikipedia-query passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"wikipedia-query can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, __now, _title, headers, hideTitle, search, language" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element, hax-register-app" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "word-count": { - "element": "word-count", - "component": { - "properties": [ - { - "name": "words", - "type": "Number", - "attribute": null, - "config": "type: Number" - }, - { - "name": "wordsPrefix", - "type": "String", - "attribute": "words-prefix", - "config": "type: String, attribute: \"words-prefix\"", - "defaultValue": "\"Word count\"" - } - ], - "slots": [ - "default" - ], - "events": [], - "methods": [ - "update", - "if" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [ - "I18NMixin" - ] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../word-count.js\";\n\ndescribe(\"word-count test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(html`\n \n `);\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n/*\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"word-count passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"word-count passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"word-count can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: words, wordsPrefix" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ], - "needsWork": true, - "priority": "HIGH" - }, - "wysiwyg-hax": { - "element": "wysiwyg-hax", - "component": { - "properties": [ - { - "name": "openDefault", - "type": "Boolean", - "attribute": "open-default", - "config": "type: Boolean,\n attribute: \"open-default\"," - }, - { - "name": "redirectLocation", - "type": "String", - "attribute": "redirect-location", - "config": "type: String,\n attribute: \"redirect-location\"," - }, - { - "name": "hidePanelOps", - "type": "Boolean", - "attribute": "hide-panel-ops", - "config": "type: Boolean,\n attribute: \"hide-panel-ops\"," - }, - { - "name": "elementAlign", - "type": "String", - "attribute": "element-align", - "config": "type: String,\n attribute: \"element-align\"," - }, - { - "name": "offsetMargin", - "type": "String", - "attribute": "offset-margin", - "config": "type: String,\n attribute: \"offset-margin\"," - }, - { - "name": "bodyValue", - "type": "String", - "attribute": "body-value", - "config": "type: String,\n attribute: \"body-value\"," - }, - { - "name": "allowedTags", - "type": "Array", - "attribute": "allowed-tags", - "config": "type: Array,\n attribute: \"allowed-tags\"," - }, - { - "name": "appStoreConnection", - "type": "String", - "attribute": "app-store-connection", - "config": "type: String,\n attribute: \"app-store-connection\"," - }, - { - "name": "saveButtonSelector", - "type": "Object", - "attribute": null, - "config": "type: Object," - }, - { - "name": "fieldClass", - "type": "String", - "attribute": "field-class", - "config": "type: String,\n attribute: \"field-class\"," - }, - { - "name": "fieldId", - "type": "String", - "attribute": "field-id", - "config": "type: String,\n attribute: \"field-id\"," - }, - { - "name": "fieldName", - "type": "String", - "attribute": "field-name", - "config": "type: String,\n attribute: \"field-name\"," - }, - { - "name": "syncBody", - "type": "Boolean", - "attribute": "sync-body", - "config": "type: Boolean,\n attribute: \"sync-body\",\n reflect: true," - }, - { - "name": "endPoint", - "type": "String", - "attribute": "end-point", - "config": "type: String,\n attribute: \"end-point\"," - }, - { - "name": "updatePageData", - "type": "String", - "attribute": "update-page-data", - "config": "type: String,\n attribute: \"update-page-data\"," - } - ], - "slots": [], - "events": [ - "hax-save", - "simple-modal-hide" - ], - "methods": [ - "if", - "createRenderRoot" - ], - "hasHaxProperties": false, - "extendsLitElement": true, - "extendsOtherBehaviors": [] - }, - "tests": { - "exists": true, - "hasAccessibilityTests": true, - "hasPropertyTests": false, - "hasAttributeTests": true, - "hasSlotTests": false, - "hasEventTests": false, - "hasMethodTests": false, - "content": "import { fixture, expect, html } from \"@open-wc/testing\";\n\nimport \"../wysiwyg-hax.js\";\n/*\ndescribe(\"wysiwyg-hax test\", () => {\n let element;\n beforeEach(async () => {\n element = await fixture(\n html` `\n );\n });\n\n it(\"passes the a11y audit\", async () => {\n await expect(element).shadowDom.to.be.accessible();\n });\n});\n\n\ndescribe(\"A11y/chai axe tests\", () => {\n it(\"wysiwyg-hax passes accessibility test\", async () => {\n const el = await fixture(html` `);\n await expect(el).to.be.accessible();\n });\n it(\"wysiwyg-hax passes accessibility negation\", async () => {\n const el = await fixture(\n html``\n );\n await assert.isNotAccessible(el);\n });\n});\n\n/*\n// Custom properties test\ndescribe(\"Custom Property Test\", () => {\n it(\"wysiwyg-hax can instantiate a element with custom properties\", async () => {\n const el = await fixture(html``);\n expect(el.foo).to.equal('bar');\n })\n})\n*/\n\n/*\n// Test if element is mobile responsive\ndescribe('Test Mobile Responsiveness', () => {\n before(async () => {z \n await setViewport({width: 375, height: 750});\n })\n it('sizes down to 360px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('360px');\n })\n}) */\n\n/*\n// Test if element sizes up for desktop behavior\ndescribe('Test Desktop Responsiveness', () => {\n before(async () => {\n await setViewport({width: 1000, height: 1000});\n })\n it('sizes up to 410px', async () => {\n const el = await fixture(html``);\n const width = getComputedStyle(el).width;\n expect(width).to.equal('410px');\n })\n it('hides mobile menu', async () => {\n const el await fixture(html``);\n const hidden = el.getAttribute('hidden');\n expect(hidden).to.equal(true);\n })\n}) */\n" - }, - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: openDefault, redirectLocation, hidePanelOps, elementAlign, offsetMargin, bodyValue, allowedTags, appStoreConnection, saveButtonSelector, fieldClass, fieldId, fieldName, syncBody, endPoint, updatePageData" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: hax-save, simple-modal-hide" - } - ], - "needsWork": true, - "priority": "HIGH" - } - } -} \ No newline at end of file diff --git a/test-audit-system.js b/test-audit-system.js deleted file mode 100644 index 928cbefbdb..0000000000 --- a/test-audit-system.js +++ /dev/null @@ -1,379 +0,0 @@ -#!/usr/bin/env node - -const fs = require('fs'); -const path = require('path'); -const { execSync } = require('child_process'); - -/** - * Parse a JavaScript file to extract component properties, slots, events, and methods - */ -function analyzeComponent(elementPath, elementName) { - const jsFilePath = path.join(elementPath, `${elementName}.js`); - - if (!fs.existsSync(jsFilePath)) { - return { - error: `Main JS file not found: ${jsFilePath}`, - properties: [], - slots: [], - events: [], - methods: [] - }; - } - - const content = fs.readFileSync(jsFilePath, 'utf8'); - - const analysis = { - properties: [], - slots: [], - events: [], - methods: [], - hasHaxProperties: false, - extendsLitElement: false, - extendsOtherBehaviors: [] - }; - - try { - // Extract properties from static get properties() - const propertiesMatch = content.match(/static\s+get\s+properties\s*\(\s*\)\s*\{[\s\S]*?return\s*\{([\s\S]*?)\}\s*;?\s*\}/); - if (propertiesMatch) { - const propertiesBlock = propertiesMatch[1]; - - // Parse individual properties - const propertyMatches = propertiesBlock.matchAll(/(\w+)\s*:\s*\{([^}]*)\}/g); - for (const match of propertyMatches) { - const propName = match[1]; - const propConfig = match[2]; - - let type = 'String'; // default - let attribute = null; - let defaultValue = null; - - const typeMatch = propConfig.match(/type\s*:\s*(\w+)/); - if (typeMatch) type = typeMatch[1]; - - const attributeMatch = propConfig.match(/attribute\s*:\s*["']([^"']+)["']/); - if (attributeMatch) attribute = attributeMatch[1]; - - analysis.properties.push({ - name: propName, - type, - attribute, - config: propConfig.trim() - }); - } - } - - // Extract default values from constructor - const constructorMatch = content.match(/constructor\s*\(\s*\)\s*\{([\s\S]*?)\}/); - if (constructorMatch) { - const constructorBody = constructorMatch[1]; - analysis.properties.forEach(prop => { - const defaultMatch = constructorBody.match(new RegExp(`this\\.${prop.name}\\s*=\\s*([^;]+);`)); - if (defaultMatch) { - prop.defaultValue = defaultMatch[1].trim(); - } - }); - } - - // Look for slots in render method - const renderMatch = content.match(/render\s*\(\s*\)\s*\{[\s\S]*?return\s+html`([\s\S]*?)`;?\s*\}/); - if (renderMatch) { - const template = renderMatch[1]; - - // Find slot elements - const slotMatches = template.matchAll(/]*>/g); - for (const match of slotMatches) { - const slotName = match[1] || 'default'; - if (!analysis.slots.includes(slotName)) { - analysis.slots.push(slotName); - } - } - } - - // Look for event dispatching - const eventMatches = content.matchAll(/(?:this\.)?dispatchEvent\s*\(\s*new\s+(?:CustomEvent|Event)\s*\(\s*["']([^"']+)["']/g); - for (const match of eventMatches) { - if (!analysis.events.includes(match[1])) { - analysis.events.push(match[1]); - } - } - - // Check for fire/fireEvent methods (common pattern) - const fireMatches = content.matchAll(/(?:this\.)?fire(?:Event)?\s*\(\s*["']([^"']+)["']/g); - for (const match of fireMatches) { - if (!analysis.events.includes(match[1])) { - analysis.events.push(match[1]); - } - } - - // Check if it has haxProperties - analysis.hasHaxProperties = content.includes('haxProperties') || content.includes('getHaxProperties'); - - // Check inheritance - analysis.extendsLitElement = content.includes('extends LitElement') || content.includes('extends') && content.includes('LitElement'); - - // Look for behavior mixins - const behaviorMatches = content.matchAll(/extends\s+(\w+)\s*\(/g); - for (const match of behaviorMatches) { - analysis.extendsOtherBehaviors.push(match[1]); - } - - // Extract public methods (non-private/protected) - const methodMatches = content.matchAll(/(?:^|\n)\s*([a-zA-Z][a-zA-Z0-9]*)\s*\([^)]*\)\s*\{/g); - for (const match of methodMatches) { - const methodName = match[1]; - // Skip constructor, render, and common lifecycle methods, and private methods - if (!methodName.startsWith('_') && - !['constructor', 'render', 'updated', 'firstUpdated', 'connectedCallback', 'disconnectedCallback', 'get', 'set', 'static'].includes(methodName)) { - if (!analysis.methods.includes(methodName)) { - analysis.methods.push(methodName); - } - } - } - - } catch (error) { - analysis.error = `Error parsing component: ${error.message}`; - } - - return analysis; -} - -/** - * Analyze existing test file - */ -function analyzeExistingTest(elementPath, elementName) { - const testFilePath = path.join(elementPath, 'test', `${elementName}.test.js`); - - if (!fs.existsSync(testFilePath)) { - return { - exists: false, - hasAccessibilityTests: false, - testedProperties: [], - testedSlots: [], - testedEvents: [], - testedMethods: [] - }; - } - - const content = fs.readFileSync(testFilePath, 'utf8'); - - return { - exists: true, - hasAccessibilityTests: content.includes('to.be.accessible()'), - hasPropertyTests: content.includes('Property type validation') || content.includes('property'), - hasAttributeTests: content.includes('Attribute') || content.includes('attribute'), - hasSlotTests: content.includes('slot'), - hasEventTests: content.includes('dispatchEvent') || content.includes('fire'), - hasMethodTests: content.includes('method'), - content: content - }; -} - -/** - * Generate test improvement recommendations - */ -function generateRecommendations(componentAnalysis, testAnalysis, elementName) { - const recommendations = []; - - if (!testAnalysis.exists) { - recommendations.push({ - priority: 'HIGH', - type: 'MISSING_TEST_FILE', - message: `No test file found. Create test/\${elementName}.test.js` - }); - return recommendations; - } - - if (!testAnalysis.hasAccessibilityTests) { - recommendations.push({ - priority: 'HIGH', - type: 'MISSING_A11Y_TESTS', - message: 'Add accessibility tests using await expect(element).shadowDom.to.be.accessible()' - }); - } - - if (componentAnalysis.properties.length > 0 && !testAnalysis.hasPropertyTests) { - recommendations.push({ - priority: 'HIGH', - type: 'MISSING_PROPERTY_TESTS', - message: `Add comprehensive property tests for: ${componentAnalysis.properties.map(p => p.name).join(', ')}` - }); - } - - if (componentAnalysis.slots.length > 0 && !testAnalysis.hasSlotTests) { - recommendations.push({ - priority: 'MEDIUM', - type: 'MISSING_SLOT_TESTS', - message: `Add slot tests for: ${componentAnalysis.slots.join(', ')}` - }); - } - - if (componentAnalysis.events.length > 0 && !testAnalysis.hasEventTests) { - recommendations.push({ - priority: 'MEDIUM', - type: 'MISSING_EVENT_TESTS', - message: `Add event tests for: ${componentAnalysis.events.join(', ')}` - }); - } - - // Check for attributes that should have tests - const attributeProps = componentAnalysis.properties.filter(p => p.attribute); - if (attributeProps.length > 0 && !testAnalysis.hasAttributeTests) { - recommendations.push({ - priority: 'HIGH', - type: 'MISSING_ATTRIBUTE_TESTS', - message: `Add attribute-to-property mapping tests for: ${attributeProps.map(p => p.attribute).join(', ')}` - }); - } - - return recommendations; -} - -/** - * Main audit function - */ -function auditElement(elementName) { - const elementPath = path.join(__dirname, 'elements', elementName); - - if (!fs.existsSync(elementPath)) { - return { - element: elementName, - error: `Element directory not found: ${elementPath}` - }; - } - - const componentAnalysis = analyzeComponent(elementPath, elementName); - const testAnalysis = analyzeExistingTest(elementPath, elementName); - const recommendations = generateRecommendations(componentAnalysis, testAnalysis, elementName); - - return { - element: elementName, - component: componentAnalysis, - tests: testAnalysis, - recommendations, - needsWork: recommendations.length > 0, - priority: recommendations.some(r => r.priority === 'HIGH') ? 'HIGH' : - recommendations.length > 0 ? 'MEDIUM' : 'LOW' - }; -} - -/** - * Audit all elements - */ -function auditAllElements() { - const elementsDir = path.join(__dirname, 'elements'); - const elements = fs.readdirSync(elementsDir).filter(item => { - const itemPath = path.join(elementsDir, item); - return fs.statSync(itemPath).isDirectory(); - }); - - const results = { - summary: { - total: elements.length, - needsWork: 0, - highPriority: 0, - mediumPriority: 0, - lowPriority: 0 - }, - elements: {} - }; - - elements.forEach(element => { - const audit = auditElement(element); - results.elements[element] = audit; - - if (audit.needsWork) { - results.summary.needsWork++; - if (audit.priority === 'HIGH') results.summary.highPriority++; - else if (audit.priority === 'MEDIUM') results.summary.mediumPriority++; - else results.summary.lowPriority++; - } - }); - - return results; -} - -/** - * Generate prioritized work list - */ -function generateWorkList(auditResults) { - const workList = []; - - // High priority items first - Object.keys(auditResults.elements) - .filter(element => auditResults.elements[element].priority === 'HIGH') - .sort() - .forEach(element => { - workList.push({ - element, - priority: 'HIGH', - recommendations: auditResults.elements[element].recommendations - }); - }); - - // Medium priority items - Object.keys(auditResults.elements) - .filter(element => auditResults.elements[element].priority === 'MEDIUM') - .sort() - .forEach(element => { - workList.push({ - element, - priority: 'MEDIUM', - recommendations: auditResults.elements[element].recommendations - }); - }); - - return workList; -} - -// Export functions for use in other scripts -module.exports = { - auditElement, - auditAllElements, - analyzeComponent, - analyzeExistingTest, - generateRecommendations, - generateWorkList -}; - -// CLI usage -if (require.main === module) { - const args = process.argv.slice(2); - - if (args.length === 0) { - console.log('Auditing all elements...'); - const results = auditAllElements(); - const workList = generateWorkList(results); - - console.log('\n='.repeat(80)); - console.log('WEBCOMPONENTS TEST AUDIT SUMMARY'); - console.log('='.repeat(80)); - console.log(`Total Elements: ${results.summary.total}`); - console.log(`Need Work: ${results.summary.needsWork}`); - console.log(`High Priority: ${results.summary.highPriority}`); - console.log(`Medium Priority: ${results.summary.mediumPriority}`); - console.log(`Low Priority: ${results.summary.lowPriority}`); - - console.log('\nWORK LIST (first 20):'); - console.log('-'.repeat(40)); - workList.slice(0, 20).forEach((item, index) => { - console.log(`${index + 1}. [${item.priority}] ${item.element}`); - item.recommendations.forEach(rec => { - console.log(` • ${rec.message}`); - }); - console.log(); - }); - - // Save detailed results - fs.writeFileSync('test-audit-results.json', JSON.stringify(results, null, 2)); - fs.writeFileSync('test-work-list.json', JSON.stringify(workList, null, 2)); - console.log('Detailed results saved to test-audit-results.json'); - console.log('Work list saved to test-work-list.json'); - - } else { - // Audit specific element - const elementName = args[0]; - const result = auditElement(elementName); - console.log(JSON.stringify(result, null, 2)); - } -} diff --git a/test-print-route.md b/test-print-route.md deleted file mode 100644 index a6459b6474..0000000000 --- a/test-print-route.md +++ /dev/null @@ -1,33 +0,0 @@ -# Print Route Implementation - -## Summary of Changes - -I've successfully implemented a print route for HAXcms that eliminates the need for the `format=print-page` URL parameter by using an internal route system. - -### Key Components: - -1. **site-print-route.js** - Route component that handles print mode -2. **Updated haxcms-site-store.js** - Added print route with theme-switching callback -3. **Theme switching** - Automatically switches to `haxcms-print-theme` when accessing the route - -### How it works: - -1. User navigates to `x/print?page=some-page-slug` -2. The store's callback automatically switches the theme to `haxcms-print-theme` -3. The route component loads the specified page content -4. The page displays in print mode using the print theme - -### Usage Examples: - -- `x/print?page=introduction` - Shows "introduction" page in print mode -- `x/print` - Shows home/first page in print mode (fallback) - -### Benefits: - -- Eliminates the need for `format=print-page` URL parameters -- Cleaner URL structure -- Leverages existing HAXcms internal route system -- Automatic theme switching -- Proper error handling and fallbacks - -The implementation is complete and ready for testing! \ No newline at end of file diff --git a/test-work-list.json b/test-work-list.json deleted file mode 100644 index 95b5d6b114..0000000000 --- a/test-work-list.json +++ /dev/null @@ -1,2584 +0,0 @@ -[ - { - "element": ".idea", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": ".vscode", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "a11y-behaviors", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_A11Y_TESTS", - "message": "Add accessibility tests using await expect(element).shadowDom.to.be.accessible()" - } - ] - }, - { - "element": "a11y-compare-image", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: activeLayer, opacity, label, position, __lower, __upper, __markers" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: active-layer" - } - ] - }, - { - "element": "a11y-details", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: closeText, openText" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: summary, details, default" - } - ] - }, - { - "element": "a11y-figure", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: imgSrc, imgAlt, __hasDetail" - } - ] - }, - { - "element": "a11y-gif-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alt, disabled, describedBy, longdesc, src, srcWithoutAnimation, __playing, __gifLoaded" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "a11y-media-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: __playerReady, allowConcurrent, audioOnly, autoplay, captionsTrack, cc, currentTime, crossorigin, disablePrintButton, disableSearch, disableScroll, disableSeek, darkTranscript, disableFullscreen, disableInteractive, height, hideElapsedTime, hideTimestamps, hideTranscript, id, lang, learningMode, linkable, localization, loop, mediaLang, mediaTitle, muted, hideYoutubeLink, playbackRate, preload, responsiveSize, search, standAlone, source, sources, stackedLayout, sticky, stickyCorner, thumbnailSrc, tracks, transcriptTrack, volume, width, youtubeId, __currentTime, __captionsOption, __cues, __loadedTracks, __playing, __preloadedDuration, __settingsOpen, __transcriptOption" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: mute-changed, transcript-downloaded, transcript-printed, play, a11y-player-playing, pause, stop, restart, backward, forward, seek, playback-rate-changed, volume-changed, cc-toggle, fullscreen-toggle, loop-toggle, play-toggle, muted-toggle, settings-toggled, player-sticky, transcript-toggle, responsive-element, a11y-player" - } - ] - }, - { - "element": "a11y-menu-button", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: currentItem, disabled, expanded, focused, hovered, keepOpenOnClick, menuItems, noOpenOnHover, offset, position, positionAlign" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: close, open, item-click" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: disabled, expanded, focused, hovered, keep-open-on-click, no-open-on-hover, offset, position, position-align" - } - ] - }, - { - "element": "a11y-tabs", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: ariaLabel, activeTab, fullWidth, disabled, hidden, iconBreakpoint, id, layoutBreakpoint, responsiveSize, sticky, __tabs, __tabButtons, __tabFocus" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: a11y-tabs-active-changed" - } - ] - }, - { - "element": "a11y-utils", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_A11Y_TESTS", - "message": "Add accessibility tests using await expect(element).shadowDom.to.be.accessible()" - } - ] - }, - { - "element": "absolute-position-behavior", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: allowOverlap, auto, fitToVisibleBounds, hidden, for, offset, sticky, position, positionAlign, justify, target, __positions" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "accent-card", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: accentBackground, accentHeading, flat, horizontal, imageAlign, imageSrc, imageValign, noBorder, ready, link, iconSize, imageWidth" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: image-corner, heading, corner, subheading, content, footer" - } - ] - }, - { - "element": "aframe-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, height, width, skyColor, ar, x, y, z, position" - } - ] - }, - { - "element": "app-hax", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: unlockComingSoon, unlockTerrible, courses, userName, activeItem, soundIcon, searchTerm, appMode, isNewUser, phrases, userMenuOpen, siteReady, basePath, token" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: jwt-login-logout, simple-modal-show, app-hax-loaded" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: base-path" - } - ] - }, - { - "element": "audio-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_A11Y_TESTS", - "message": "Add accessibility tests using await expect(element).shadowDom.to.be.accessible()" - } - ] - }, - { - "element": "awesome-explosion", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: state, stopped, playing, paused, image, sound, size, color, resetSound" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: awesome-event" - } - ] - }, - { - "element": "b-r", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: amount" - } - ] - }, - { - "element": "barcode-reader", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_A11Y_TESTS", - "message": "Add accessibility tests using await expect(element).shadowDom.to.be.accessible()" - }, - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: value, scale, hideinput" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: value-changed" - } - ] - }, - { - "element": "baseline-build-hax", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_A11Y_TESTS", - "message": "Add accessibility tests using await expect(element).shadowDom.to.be.accessible()" - } - ] - }, - { - "element": "beaker-broker", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: archive, datUrl" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: archive-changed, dat-url-changed" - } - ] - }, - { - "element": "bootstrap-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_A11Y_TESTS", - "message": "Add accessibility tests using await expect(element).shadowDom.to.be.accessible()" - }, - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: searchTerm, menuOpen, colorTheme" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: menu-open, color-theme" - } - ] - }, - { - "element": "chartist-render", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: chartTitle, chartData, chartDesc, data, dataSource, id, options, axisX, axisY" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: chartist-render-ready, chart-data-changed, data-source-changed, data-changed, chartist-render-data, chartist-render-created, chartist-render-draw" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: chart-title, chart-data, chart-desc, data, data-source" - } - ] - }, - { - "element": "chat-agent", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: isFullView, isInterfaceHidden" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: is-full-view, is-interface-hidden" - } - ] - }, - { - "element": "check-it-out", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: checkedOut, source, icon, __computedSource, type, label, filePath, modalTitle, hideExplorer, ctl, view, modal" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: checked-out, file-path, modal-title, hide-explorer" - } - ] - }, - { - "element": "citation-element", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, scope, displayMethod, creator, source, date, licenseName, licenseLink, license" - } - ] - }, - { - "element": "clean-one", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: searchTerm" - } - ] - }, - { - "element": "clean-portfolio-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: activeItem, activeParent, ancestorItem, prevSibling, nextSibling, topItems, items, activeTags, categoryTags, allTags, menuOverflow, menuOpen, copyrightYear, pageCurrent, pageTotal, siteTitle, homeLink, activeLayout, selectedTag, lastUpdated, licenseName, licenseLink, licenseImage, dataPrimary" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: data-primary" - } - ] - }, - { - "element": "clean-two", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: searchTerm, prevPage, nextPage, pageTimestamp" - } - ] - }, - { - "element": "cms-hax", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: ready, openDefault, hidePanelOps, offsetMargin, elementAlign, allowedTags, endPoint, method, appStoreConnection, __appStore, syncBody, bodyValue, hideMessage, redirectLocation, redirectOnSave, __imported" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: hax-body-content-changed, cms-hax-saved" - } - ] - }, - { - "element": "code-editor", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: libPath, title, showCodePen, readOnly, codePenData, editorValue, value, theme, mode, language, fontSize, wordWrap, autofocus, hideLineNumbers, focused, tabSize, ready" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: show-code-pen-changed, value-changed, focused-changed" - } - ] - }, - { - "element": "code-sample", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: editMode, _haxstate, copyClipboardButton, theme, type, highlightStart, highlightEnd" - } - ] - }, - { - "element": "collection-list", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: responsiveSize" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: responsive-size" - } - ] - }, - { - "element": "count-up", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: start, end, duration, noeasing, decimalplaces, separator, decimal, prefixtext, suffixtext, thresholds, rootMargin, ratio, elementVisible" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: prefix, suffix" - } - ] - }, - { - "element": "course-model", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: visible, title, src, alt" - } - ] - }, - { - "element": "csv-render", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: dataSource, loading, caption, summary, table, tableHeadings, tableData" - } - ] - }, - { - "element": "d-d-d", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: dataPulse" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: data-pulse" - } - ] - }, - { - "element": "d-d-docs", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: option, options" - } - ] - }, - { - "element": "date-card", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: month, date, day, title, startTime, endTime, location, borderSpacing" - } - ] - }, - { - "element": "deduping-fix", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "discord-embed", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: height, width, source" - } - ] - }, - { - "element": "disqus-embed", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: loadingText, pageURL, pageIdentifier, pageTitle, shortName, lang" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: loading-text, page-url, page-identifier, page-title, short-name" - } - ] - }, - { - "element": "documentation-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: label, haxSchema, imageUrl, url" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: hax-insert" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: image-url, url" - } - ] - }, - { - "element": "editable-table", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: editMode" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: toggle-edit-mode" - } - ] - }, - { - "element": "elmsln-loading", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: dark, color, contrast, size" - } - ] - }, - { - "element": "enhanced-text", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: wikipedia, vide, fixationPoint, haxcmsGlossary, haxcmsSiteLocation, haxcmsSite, haxcmsMarkAll, loading, auto" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: fixation-point, haxcms-glossary, haxcms-site-location, haxcms-site, haxcms-mark-all" - } - ] - }, - { - "element": "es-global-bridge", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "event-badge", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, image, name, position, logo, organization, tvcolor, knobcolor, sepia, blackwhite" - } - ] - }, - { - "element": "example-hax-element", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "example-haxcms-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: activeId, _items" - } - ] - }, - { - "element": "figure-label", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, description" - } - ] - }, - { - "element": "fill-in-the-blanks", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "flash-card", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: inverted, imgSource, imgKeyword, status" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: img-source, img-keyword" - } - ] - }, - { - "element": "full-width-image", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, caption" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "fullscreen-behaviors", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: fullscreen, fullscreenEnabled" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "git-corner", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, alt, corner, size" - } - ] - }, - { - "element": "github-preview", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: repo, org, __description, repoLang, __stars, __forks, __assetAvailable, extended, readmeExtended, headers, viewMoreText, notFoundText, __readmeText, branch, url, apiUrl, rawUrl, readMe" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: hax-register-app" - } - ] - }, - { - "element": "glossy-portfolio-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, siteDescription, relatedItem, isHome, items" - } - ] - }, - { - "element": "grade-book", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: displayMode, settings, disabled, loading, ready, activeStudent, activeAssignment, totalScore, scoreLock, source, sourceData, activeSubmission, database, activeRubric, assessmentView, activeGrading, activeStudentSubmissions" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: app-name" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-tag-drop, simple-tag-dragstart" - } - ] - }, - { - "element": "grid-plate", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: itemPadding, itemMargin, breakpointSm, breakpointMd, breakpointLg, breakpointXl, columns, dataHaxRay, disableResponsive, layout" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: col-${num}" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: responsive-element, disable-responsive-changed, resize" - } - ] - }, - { - "element": "h5p-element", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "hal-9000", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: toast, commands, respondsTo, debug, auto, enabled, pitch, rate, language" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: super-daemon-toast-hide, super-daemon-toast-show" - } - ] - }, - { - "element": "hax-body", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: _useristyping, haxMover, editMode, elementAlign, trayDetail, trayStatus, activeNode, canMoveElement, viewSourceToggle" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: hax-register-body, place-holder-file-drop, hax-drop-focus-event" - } - ] - }, - { - "element": "haxor-slevin", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: color, selectedPage, activeManifestIndexCounter, activeItem, stateClass, __mainPosts, __followUpPosts" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: resize" - } - ] - }, - { - "element": "hex-picker", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: value, disabled, largeDisplay" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: value-changed" - } - ] - }, - { - "element": "hexagon-loader", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: color, size, loading, items, itemCount" - } - ] - }, - { - "element": "iframe-loader", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: loading, height, width, isPDF, disabled, source" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "image-compare-slider", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: bottomAlt, bottomDescriptionId, bottomSrc, opacity, position, title, topAlt, topDescriptionId, topSrc" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: bottom-alt, bottom-description-id, bottom-src, top-alt, top-description-id, top-src" - } - ] - }, - { - "element": "image-inspector", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: noLeft, degrees, src, hoverClass" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: toolbar, default" - } - ] - }, - { - "element": "img-pan-zoom", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: src, sources, page, describedBy, dzi, fadeIn, fullscreenToggled, flipToggled, loading, hideSpinner, loaded, showNavigationControl, showNavigator, zoomPerClick, zoomPerScroll, animationTime, navPrevNextWrap, showRotationControl, minZoomImageRatio, maxZoomPixelRatio, constrainDuringPan, visibilityRatio, navigatorAutoFade, navigatorPosition, navigatorTop, navigatorBottom, navigatorLeft, navigatorRight, navigatorHeight, navigatorWidth, navigatorToggled, sequenceMode, preserveViewport, showReferenceStrip, referenceStripScroll, previousButton, nextButton, homeButton, zoomInButton, zoomOutButton, fullScreenButton" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: loading-changed, loaded-changed, zoom, page, pan, update-viewport, viewport-changed" - } - ] - }, - { - "element": "img-view-modal", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, modal" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: modal-button-click" - } - ] - }, - { - "element": "inline-audio", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, icon, aria, title, playing, shiny" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: playing-changed" - } - ] - }, - { - "element": "intersection-element", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "journey-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: _items, activeItem, ancestorItem, location, basePath, dataPrimary, siteTheme, licenseName, licenseLink, licenseImage, lastUpdated, copyrightYear, pageCurrent, pageTotal" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: data-primary, site-theme" - } - ] - }, - { - "element": "jwt-login", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: auto, refreshUrl, redirectUrl, logoutUrl, url, method, body, key, jwt" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: jwt-login-refresh-error, jwt-changed, jwt-logged-in, jwt-token, jwt-login-login-failed" - } - ] - }, - { - "element": "la-tex", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: hydrated, initialText" - } - ] - }, - { - "element": "lazy-image-helpers", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: imageLoaded" - } - ] - }, - { - "element": "learn-two-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: opened" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "license-element", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, creator, source, licenseName, licenseImage, licenseLink, license, moreLabel, moreLink, hasMore" - } - ] - }, - { - "element": "lorem-data", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: schemas" - } - ] - }, - { - "element": "lrn-gitgraph", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: commits, template, orientation, mode, reverseArrow, config" - } - ] - }, - { - "element": "lrn-table", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, csvFile, description" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "lrn-vocab", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: term" - } - ] - }, - { - "element": "lrndesign-chart", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: accentColor, dark, height, reverseData, width" - } - ] - }, - { - "element": "lrndesign-imagemap", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: label, src, hotspotDetails, subtopicOf, parent, __activeHotspot" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: heading" - } - ] - }, - { - "element": "lrndesign-timeline", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: events, timelineSize, title" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: responsive-element" - } - ] - }, - { - "element": "lunr-search", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: dataSource, __auto, data, method, search, results, noStopWords, fields, indexNoStopWords, index, __lunrLoaded, limit, minScore, log, demo" - } - ] - }, - { - "element": "map-menu", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: disabled, editControls, isFlex, isHorizontal, maxDepth, title, data, manifest, items, selected, activeItem, autoScroll, activeIndicator" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: selected, map-menu-item-hidden-check" - } - ] - }, - { - "element": "mark-the-words", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: wordList, statement, missedAnswers, numberCorrect, numberGuessed" - } - ] - }, - { - "element": "matching-question", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "md-block", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, markdown" - } - ] - }, - { - "element": "media-image", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: link, asMd, __figureLabel, modalTitle, disableZoom, _hasCaption, source, citation, caption, alt, size, round, card, box, offset, figureLabelTitle, figureLabelDescription" - } - ] - }, - { - "element": "media-quote", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "meme-maker", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alt, crossorigin, describedBy, imageUrl, topText, bottomText" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - } - ] - }, - { - "element": "merit-badge", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: badgeDate, badgeImage, badgeTitle, badgeDetails, hyperLink, badgeSkills, skillsOpened, detailsOpened, badgeUnlocked, badgeColor" - } - ] - }, - { - "element": "moment-element", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: datetime, inputFormat, outputFormat, from, to, output, libraryLoaded" - } - ] - }, - { - "element": "music-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, visualizer, noWaterfall, noVisual" - } - ] - }, - { - "element": "oer-schema", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: text, oerProperty, typeof, relatedResource" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "outline-designer", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: haxGizmos, hideDelete, activeItemForActions, storeTools, eventData, items, appReady, activePreview, activePreviewIndex, hideContentOps, fidelity, liveRegionText" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-modal-hide" - } - ] - }, - { - "element": "outline-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: opened, closed, activeId, narrow" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: title, default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: closed-changed, resize" - } - ] - }, - { - "element": "page-break", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: iconType, noderefs, relatedItems, icon, accentColor, entityType, description, order, hideInMenu, tags, developerTheme, title, slug, image, parent, published, locked, depth, itemId, breakType, status, pageType, _haxState" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: page-break-registration, page-break-change, hax-refresh-tray-form" - } - ] - }, - { - "element": "page-contents-menu", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: contentContainer, relationship, items, position, mobile, label, hideSettings, hideIfEmpty, isEmpty" - } - ] - }, - { - "element": "page-flag", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: label, opened, show" - } - ] - }, - { - "element": "page-section", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: anchor, scrollerLabel, filter, fold, full, scroller, bg, image, preset" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default, entice, buttons" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: scroller-label" - } - ] - }, - { - "element": "paper-input-flagged", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: label, disabled, icon, maxlength, minlength, status, value, flaggedInput, inputSuccess, __activeMessage" - } - ] - }, - { - "element": "paper-stepper", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: selected, progressBar, backLabel, nextLabel, disablePrevious, disableNext, noButtons" - } - ] - }, - { - "element": "parallax-image", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: imageBg, describedBy" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: parallax_heading" - } - ] - }, - { - "element": "pdf-browser-viewer", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: file, notSupportedMessage, notSupportedLinkMessage, height, width, card, downloadLabel, elevation" - } - ] - }, - { - "element": "person-testimonial", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: describedBy, image, name, position" - } - ] - }, - { - "element": "place-holder", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: iconFromType, text, directions, calcText, type, dragOver" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: place-holder-replace, place-holder-file-drop" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: drag-over" - } - ] - }, - { - "element": "play-list", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: items, loop, edit, navigation, pagination, aspectRatio, orientation, slide" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: aspect-ratio" - } - ] - }, - { - "element": "polaris-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: searchTerm, siteDescription, imageLink, image, imageAlt, pageTimestamp" - } - ] - }, - { - "element": "post-card", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: t, to, from, message, photoSrc, stampSrc, postMarkLocations" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: photo-src, stamp-src, post-mark-locations" - } - ] - }, - { - "element": "product-card", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: disabled, heading, subheading, icon, hasDemo" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: product-card-demo-show" - } - ] - }, - { - "element": "product-glance", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, subtitle, icon" - } - ] - }, - { - "element": "product-offering", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alt, source, icon, title, _titleOne, _titleTwo, description" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: description" - } - ] - }, - { - "element": "progress-donut", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: animation, animationDelay, complete, desc, imageSrc, imageAlt" - } - ] - }, - { - "element": "q-r", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: data, title, modulesize, margin, format" - } - ] - }, - { - "element": "radio-behaviors", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: itemData, selection" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: selection-changed" - } - ] - }, - { - "element": "relative-heading", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: closeIcon, closeLabel, copyMessage, disableLink, linkAlignRight, linkIcon, linkLabel" - } - ] - }, - { - "element": "retro-card", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, subtitle, tags, mediaSource, url, hoverSource, hoverState, __cardTags, nosource, __source" - } - ] - }, - { - "element": "rich-text-editor", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: id, contenteditable, disabled, disableHover, placeholder, toolbarId, range, rawhtml, type, viewSource, __codeEditorValue, __needsUpdate, __focused, __hovered" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: focus, contenteditable-change" - } - ] - }, - { - "element": "rpg-character", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: literalseed, accessories, height, width, base, face, faceItem, hair, pants, shirt, skin, hatColor, hat, walking, leg, seed, speed, circle, fire, demo, reduceMotion" - } - ] - }, - { - "element": "runkit-embed", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "schema-behaviors", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: schemaMap" - } - ] - }, - { - "element": "scroll-button", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: target, icon, label, _label, position, t" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - } - ] - }, - { - "element": "self-check", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, question, image, alt, describedBy, link, correct" - } - ] - }, - { - "element": "simple-autocomplete", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: opened, hideInput, selectionPosition, value, itemLimit" - } - ] - }, - { - "element": "simple-blog", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: selectedPage" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: resize" - } - ] - }, - { - "element": "simple-colors", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: accentColor, dark" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "simple-colors-shared-styles", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: colors, contrasts" - } - ] - }, - { - "element": "simple-cta", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: link, label, icon, editMode, hideIcon, large, light, hotline, saturate, disabled" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "simple-datetime", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: timestamp, format, date, unix" - } - ] - }, - { - "element": "simple-fields", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: disableResponsive, fields, schematizer, label, __activeTabs, codeTheme" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: active-tabs-changed" - } - ] - }, - { - "element": "simple-filter", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: items, like, where, caseSensitive, multiMatch, filtered" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: filter, filtered-changed" - } - ] - }, - { - "element": "simple-icon", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: contrast" - } - ] - }, - { - "element": "simple-icon-picker", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: allowNull, icons, includeSets, excludeSets, exclude, value, optionsPerRow, __iconList" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: value-changed" - } - ] - }, - { - "element": "simple-login", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: username, password, loading, userInputLabel, userInputErrMsg, passwordInputLabel, passwordInputErrMsg, loginBtnText" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-login-login" - } - ] - }, - { - "element": "simple-modal", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, opened, closeLabel, closeIcon, invokedBy, modal, mode" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-toast-hide" - } - ] - }, - { - "element": "simple-picker", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: allowNull, alignRight, ariaLabelledby, blockLabel, disabled, expanded, hideOptionLabels, hideNullOption, hideSample, justify, label, options, titleAsHtml, value, __activeDesc, __options, __selectedOption, __ready" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: changed, click, mousedown, keydown, option-focus, value-changed, change, expand, collapse" - } - ] - }, - { - "element": "simple-range-input", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: dragging, immediateValue, value, min, step, max, label, disabled" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: immediate-value-changed, value-changed" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: immediate-value" - } - ] - }, - { - "element": "simple-search", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alwaysFloatLabel, caseSensitive, controls, inline, nextButtonIcon, nextButtonLabel, noLabelFloat, prevButtonIcon, prevButtonLabel, resultCount, resultPointer, selector, searchInputIcon, searchInputLabel, searchTerms, target, __hideNext, __hidePrev" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-search, goto-result" - } - ] - }, - { - "element": "simple-toast", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: opened, text, classStyle, closeText, duration, eventCallback, closeButton" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "simple-toolbar", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: alwaysExpanded, ariaControls, ariaLabel, collapsed, config, id, moreShortcuts, shortcutKeys, sticky, __buttons, collapseDisabled, __focused, __hovered" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: shortcut-key-pressed" - } - ] - }, - { - "element": "simple-tooltip", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: for, manualMode, position, fitToVisibleBounds, offset, marginTop, animationDelay, animationEntry, animationExit, _showing" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "social-share-link", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: buttonStyle, image, message, mode, text, type, url, __href, __icon, __linkText, __showIcon" - } - ] - }, - { - "element": "sorting-question", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: numberCorrect" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: simple-toast-hide, user-engagement" - } - ] - }, - { - "element": "spotify-embed", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: source, theme, size, playlistid, type, editing" - } - ] - }, - { - "element": "star-rating", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: score, possible, interactive, numStars, _calPercent" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: star-rating-click" - } - ] - }, - { - "element": "stop-note", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, url, icon, status" - } - ] - }, - { - "element": "super-daemon", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: santaMode, opened, loading, key1, key2, icon, items, programResults, programName, allItems, context, commandContext, program, programSearch, like, value, mini, wand, activeNode, programTarget, voiceSearch, voiceCommands, listeningForInput" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: super-daemon-close, super-daemon-toast-hide, super-daemon-command-context-changed, super-daemon-context-changed" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: santa-mode, voice-search, listening-for-input" - } - ] - }, - { - "element": "tagging-question", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "training-theme", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: items, activeId, time, prevPage, nextPage, maxIndex" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "twitter-embed", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: tweet, _haxstate, lang, dataWidth, dataTheme, tweetId, noPopups, allowPopups" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - } - ] - }, - { - "element": "type-writer", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: delay, cursorDuration, text, speed, elementVisible, eraseSpeed, typing, _length, _oldText" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: type-writer-end" - } - ] - }, - { - "element": "un-sdg", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: loading, fetchpriority, goal, colorOnly, alt" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: color-only" - } - ] - }, - { - "element": "undo-manager", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: canUndo, canRedo, undoStackObserverProps, target, stack" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: stack-changed, can-undo-changed, can-redo-changed" - } - ] - }, - { - "element": "utils", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_TEST_FILE", - "message": "No test file found. Create test/${elementName}.test.js" - } - ] - }, - { - "element": "video-player", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: sourceType, accentColor, crossorigin, dark, darkTranscript, disableInteractive, height, hideTimestamps, hideTranscript, id, learningMode, lang, linkable, mediaTitle, hideYoutubeLink, source, sources, sourceData, stickyCorner, track, tracks, thumbnailSrc, width, playing, allowBackgroundPlay, startTime, endTime" - } - ] - }, - { - "element": "vocab-term", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: popoverMode, detailsOpen" - } - ] - }, - { - "element": "voice-recorder", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: recording, label" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: voice-recorder-recording-blob" - } - ] - }, - { - "element": "web-container", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: fname, status, files, filesShown, hideTerminal, hideEditor, hideWindow" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: web-container-dependencies-installing, web-container-dependencies-installed, web-container-npm-start, web-container-server-ready, web-container-command-start, web-container-command-finished" - }, - { - "priority": "HIGH", - "type": "MISSING_ATTRIBUTE_TESTS", - "message": "Add attribute-to-property mapping tests for: hide-terminal, hide-editor, hide-window" - } - ] - }, - { - "element": "wikipedia-query", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: title, __now, _title, headers, hideTitle, search, language" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element, hax-register-app" - } - ] - }, - { - "element": "word-count", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: words, wordsPrefix" - }, - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "wysiwyg-hax", - "priority": "HIGH", - "recommendations": [ - { - "priority": "HIGH", - "type": "MISSING_PROPERTY_TESTS", - "message": "Add comprehensive property tests for: openDefault, redirectLocation, hidePanelOps, elementAlign, offsetMargin, bodyValue, allowedTags, appStoreConnection, saveButtonSelector, fieldClass, fieldId, fieldName, syncBody, endPoint, updatePageData" - }, - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: hax-save, simple-modal-hide" - } - ] - }, - { - "element": "a11y-carousel", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: above, img, default" - } - ] - }, - { - "element": "course-design", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "dynamic-import-registry", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: dynamic-import-registry-loaded, dynamic-import-registry-failure" - } - ] - }, - { - "element": "future-terminal-text", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "h-a-x", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: hax-save-body-value" - } - ] - }, - { - "element": "lesson-overview", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: prefix, default" - } - ] - }, - { - "element": "pouch-db", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: pouch-db-show-data" - } - ] - }, - { - "element": "responsive-utility", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "simple-emoji", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "simple-popover", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "terrible-themes", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_SLOT_TESTS", - "message": "Add slot tests for: default" - } - ] - }, - { - "element": "user-action", - "priority": "MEDIUM", - "recommendations": [ - { - "priority": "MEDIUM", - "type": "MISSING_EVENT_TESTS", - "message": "Add event tests for: i18n-manager-register-element" - } - ] - } -] \ No newline at end of file diff --git a/wc-registry.json b/wc-registry.json index 04cb9e1092..e2f513fb3a 100644 --- a/wc-registry.json +++ b/wc-registry.json @@ -1 +1 @@ -{"focus-trap":"@a11y/focus-trap/focus-trap.js","local-time":"@github/time-elements/dist/local-time-element.js","relative-time":"@github/time-elements/dist/relative-time-element.js","time-ago":"@github/time-elements/dist/time-ago-element.js","time-until":"@github/time-elements/dist/time-until-element.js","model-viewer":"@google/model-viewer/dist/model-viewer.js","a11y-carousel":"@haxtheweb/a11y-carousel/a11y-carousel.js","a11y-carousel-button":"@haxtheweb/a11y-carousel/lib/a11y-carousel-button.js","a11y-collapse":"@haxtheweb/a11y-collapse/a11y-collapse.js","a11y-collapse-group":"@haxtheweb/a11y-collapse/lib/a11y-collapse-group.js","a11y-compare-image":"@haxtheweb/a11y-compare-image/a11y-compare-image.js","a11y-details":"@haxtheweb/a11y-details/a11y-details.js","a11y-figure":"@haxtheweb/a11y-figure/a11y-figure.js","a11y-gif-player":"@haxtheweb/a11y-gif-player/a11y-gif-player.js","a11y-media-player":"@haxtheweb/a11y-media-player/a11y-media-player.js","a11y-media-button":"@haxtheweb/a11y-media-player/lib/a11y-media-button.js","a11y-media-play-button":"@haxtheweb/a11y-media-player/lib/a11y-media-play-button.js","a11y-media-state-manager":"@haxtheweb/a11y-media-player/lib/a11y-media-state-manager.js","a11y-media-transcript-cue":"@haxtheweb/a11y-media-player/lib/a11y-media-transcript-cue.js","a11y-media-youtube":"@haxtheweb/a11y-media-player/lib/a11y-media-youtube.js","a11y-menu-button":"@haxtheweb/a11y-menu-button/a11y-menu-button.js","a11y-menu-button-item":"@haxtheweb/a11y-menu-button/lib/a11y-menu-button-item.js","a11y-tabs":"@haxtheweb/a11y-tabs/a11y-tabs.js","a11y-tab":"@haxtheweb/a11y-tabs/lib/a11y-tab.js","absolute-position-behavior":"@haxtheweb/absolute-position-behavior/absolute-position-behavior.js","absolute-position-state-manager":"@haxtheweb/absolute-position-behavior/lib/absolute-position-state-manager.js","accent-card":"@haxtheweb/accent-card/accent-card.js","aframe-player":"@haxtheweb/aframe-player/aframe-player.js","air-horn":"@haxtheweb/air-horn/air-horn.js","app-hax":"@haxtheweb/app-hax/app-hax.js","app-hax-theme":"@haxtheweb/app-hax/lib/app-hax-theme.js","random-word":"@haxtheweb/app-hax/lib/random-word/random-word.js","rpg-character-toast":"@haxtheweb/app-hax/lib/rpg-character-toast/rpg-character-toast.js","app-hax-button":"@haxtheweb/app-hax/lib/v1/app-hax-button.js","app-hax-hat-progress":"@haxtheweb/app-hax/lib/v1/app-hax-hat-progress.js","app-hax-label":"@haxtheweb/app-hax/lib/v1/app-hax-label.js","app-hax-search-bar":"@haxtheweb/app-hax/lib/v1/app-hax-search-bar.js","app-hax-search-results":"@haxtheweb/app-hax/lib/v1/app-hax-search-results.js","app-hax-site-bar":"@haxtheweb/app-hax/lib/v1/app-hax-site-bar.js","app-hax-site-button":"@haxtheweb/app-hax/lib/v1/app-hax-site-button.js","app-hax-site-details":"@haxtheweb/app-hax/lib/v1/app-hax-site-details.js","app-hax-site-login":"@haxtheweb/app-hax/lib/v1/app-hax-site-login.js","app-hax-steps":"@haxtheweb/app-hax/lib/v1/app-hax-steps.js","app-hax-toast":"@haxtheweb/app-hax/lib/v1/app-hax-toast.js","app-hax-top-bar":"@haxtheweb/app-hax/lib/v1/app-hax-top-bar.js","app-hax-user-menu-button":"@haxtheweb/app-hax/lib/v1/app-hax-user-menu-button.js","app-hax-user-menu":"@haxtheweb/app-hax/lib/v1/app-hax-user-menu.js","app-hax-wired-toggle":"@haxtheweb/app-hax/lib/v1/app-hax-wired-toggle.js","app-hax-backend-api":"@haxtheweb/app-hax/lib/v1/AppHaxBackendAPI.js","app-hax-router":"@haxtheweb/app-hax/lib/v1/AppHaxRouter.js","wired-darkmode-toggle":"@haxtheweb/app-hax/lib/wired-darkmode-toggle/wired-darkmode-toggle.js","audio-player":"@haxtheweb/audio-player/audio-player.js","author-card":"@haxtheweb/author-card/author-card.js","awesome-explosion":"@haxtheweb/awesome-explosion/awesome-explosion.js","b-r":"@haxtheweb/b-r/b-r.js","barcode-reader":"@haxtheweb/barcode-reader/barcode-reader.js","beaker-broker":"@haxtheweb/beaker-broker/beaker-broker.js","bootstrap-theme":"@haxtheweb/bootstrap-theme/bootstrap-theme.js","bootstrap-breadcrumb":"@haxtheweb/bootstrap-theme/lib/BootstrapBreadcrumb.js","bootstrap-footer":"@haxtheweb/bootstrap-theme/lib/BootstrapFooter.js","bootstrap-search":"@haxtheweb/bootstrap-theme/lib/BootstrapSearch.js","chartist-render":"@haxtheweb/chartist-render/chartist-render.js","chat-agent":"@haxtheweb/chat-agent/chat-agent.js","chat-button":"@haxtheweb/chat-agent/lib/chat-button.js","chat-control-bar":"@haxtheweb/chat-agent/lib/chat-control-bar.js","chat-developer-panel":"@haxtheweb/chat-agent/lib/chat-developer-panel.js","chat-input":"@haxtheweb/chat-agent/lib/chat-input.js","chat-interface":"@haxtheweb/chat-agent/lib/chat-interface.js","chat-message":"@haxtheweb/chat-agent/lib/chat-message.js","chat-suggestion":"@haxtheweb/chat-agent/lib/chat-suggestion.js","check-it-out":"@haxtheweb/check-it-out/check-it-out.js","citation-element":"@haxtheweb/citation-element/citation-element.js","clean-one":"@haxtheweb/clean-one/clean-one.js","clean-one-search-box":"@haxtheweb/clean-one/lib/clean-one-search-box.js","clean-portfolio-theme":"@haxtheweb/clean-portfolio-theme/clean-portfolio-theme.js","clean-two":"@haxtheweb/clean-two/clean-two.js","cms-hax":"@haxtheweb/cms-hax/cms-hax.js","cms-block":"@haxtheweb/cms-hax/lib/cms-block.js","cms-entity":"@haxtheweb/cms-hax/lib/cms-entity.js","cms-token":"@haxtheweb/cms-hax/lib/cms-token.js","cms-views":"@haxtheweb/cms-hax/lib/cms-views.js","code-editor":"@haxtheweb/code-editor/code-editor.js","code-pen-button":"@haxtheweb/code-editor/lib/code-pen-button.js","monaco-element":"@haxtheweb/code-editor/lib/monaco-element/monaco-element.js","code-sample":"@haxtheweb/code-sample/code-sample.js","collection-list":"@haxtheweb/collection-list/collection-list.js","collection-item":"@haxtheweb/collection-list/lib/collection-item.js","collection-row":"@haxtheweb/collection-list/lib/collection-row.js","collections-theme-banner":"@haxtheweb/collection-list/lib/collections-theme-banner.js","collections-theme":"@haxtheweb/collection-list/lib/collections-theme.js","count-up":"@haxtheweb/count-up/count-up.js","course-design":"@haxtheweb/course-design/course-design.js","activity-box":"@haxtheweb/course-design/lib/activity-box.js","block-quote":"@haxtheweb/course-design/lib/block-quote.js","course-intro-header":"@haxtheweb/course-design/lib/course-intro-header.js","course-intro-lesson-plan":"@haxtheweb/course-design/lib/course-intro-lesson-plan.js","course-intro-lesson-plans":"@haxtheweb/course-design/lib/course-intro-lesson-plans.js","course-intro":"@haxtheweb/course-design/lib/course-intro.js","ebook-button":"@haxtheweb/course-design/lib/ebook-button.js","learning-component":"@haxtheweb/course-design/lib/learning-component.js","lrn-h5p":"@haxtheweb/course-design/lib/lrn-h5p.js","responsive-iframe":"@haxtheweb/course-design/lib/responsive-iframe.js","worksheet-download":"@haxtheweb/course-design/lib/worksheet-download.js","course-model":"@haxtheweb/course-model/course-model.js","model-info":"@haxtheweb/course-model/lib/model-info.js","model-option":"@haxtheweb/course-model/lib/model-option.js","csv-render":"@haxtheweb/csv-render/csv-render.js","d-d-d":"@haxtheweb/d-d-d/d-d-d.js","ddd-brochure-theme":"@haxtheweb/d-d-d/lib/ddd-brochure-theme.js","ddd-card":"@haxtheweb/d-d-d/lib/ddd-card.js","ddd-steps-list-item":"@haxtheweb/d-d-d/lib/ddd-steps-list-item.js","ddd-steps-list":"@haxtheweb/d-d-d/lib/ddd-steps-list.js","design-system":"@haxtheweb/d-d-d/lib/DesignSystemManager.js","mini-map":"@haxtheweb/d-d-d/lib/mini-map.js","d-d-docs":"@haxtheweb/d-d-docs/d-d-docs.js","data-viz":"@haxtheweb/data-viz/data-viz.js","date-card":"@haxtheweb/date-card/date-card.js","date-chip":"@haxtheweb/date-card/lib/date-chip.js","discord-embed":"@haxtheweb/discord-embed/discord-embed.js","disqus-embed":"@haxtheweb/disqus-embed/disqus-embed.js","haxcms-site-disqus":"@haxtheweb/disqus-embed/lib/haxcms-site-disqus.js","documentation-player":"@haxtheweb/documentation-player/documentation-player.js","dynamic-import-registry":"@haxtheweb/dynamic-import-registry/dynamic-import-registry.js","editable-outline":"@haxtheweb/editable-outline/editable-outline.js","editable-table":"@haxtheweb/editable-table/editable-table.js","editable-table-display":"@haxtheweb/editable-table/lib/editable-table-display.js","editable-table-edit":"@haxtheweb/editable-table/lib/editable-table-edit.js","editable-table-editor-rowcol":"@haxtheweb/editable-table/lib/editable-table-editor-rowcol.js","editable-table-filter":"@haxtheweb/editable-table/lib/editable-table-filter.js","editable-table-sort":"@haxtheweb/editable-table/lib/editable-table-sort.js","elmsln-loading":"@haxtheweb/elmsln-loading/elmsln-loading.js","enhanced-text":"@haxtheweb/enhanced-text/enhanced-text.js","event-badge":"@haxtheweb/event-badge/event-badge.js","example-hax-element":"@haxtheweb/example-hax-element/example-hax-element.js","example-haxcms-theme":"@haxtheweb/example-haxcms-theme/example-haxcms-theme.js","figure-label":"@haxtheweb/figure-label/figure-label.js","file-system-broker":"@haxtheweb/file-system-broker/file-system-broker.js","docx-file-system-broker":"@haxtheweb/file-system-broker/lib/docx-file-system-broker.js","xlsx-file-system-broker":"@haxtheweb/file-system-broker/lib/xlsx-file-system-broker.js","fill-in-the-blanks":"@haxtheweb/fill-in-the-blanks/fill-in-the-blanks.js","flash-card":"@haxtheweb/flash-card/flash-card.js","flash-card-answer-box":"@haxtheweb/flash-card/lib/flash-card-answer-box.js","flash-card-image-prompt":"@haxtheweb/flash-card/lib/flash-card-prompt-img.js","flash-card-set":"@haxtheweb/flash-card/lib/flash-card-set.js","fluid-type":"@haxtheweb/fluid-type/fluid-type.js","full-width-image":"@haxtheweb/full-width-image/full-width-image.js","fullscreen-behaviors":"@haxtheweb/fullscreen-behaviors/fullscreen-behaviors.js","future-terminal-text":"@haxtheweb/future-terminal-text/future-terminal-text.js","future-terminal-text-lite":"@haxtheweb/future-terminal-text/lib/future-terminal-text-lite.js","git-corner":"@haxtheweb/git-corner/git-corner.js","github-preview":"@haxtheweb/github-preview/github-preview.js","github-rpg-contributors":"@haxtheweb/github-preview/lib/github-rpg-contributors.js","wc-markdown":"@haxtheweb/github-preview/lib/wc-markdown.js","glossy-portfolio-theme":"@haxtheweb/glossy-portfolio-theme/glossy-portfolio-theme.js","glossy-portfolio-about":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-about.js","glossy-portfolio-breadcrumb":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-breadcrumb.js","glossy-portfolio-card":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-card.js","glossy-portfolio-footer":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-footer.js","glossy-portfolio-grid":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-grid.js","glossy-portfolio-header":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-header.js","glossy-portfolio-home":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-home.js","grade-book":"@haxtheweb/grade-book/grade-book.js","grade-book-lite":"@haxtheweb/grade-book/lib/grade-book-lite.js","grade-book-pop-up":"@haxtheweb/grade-book/lib/grade-book-pop-up.js","grade-book-store":"@haxtheweb/grade-book/lib/grade-book-store.js","grade-book-student-block":"@haxtheweb/grade-book/lib/grade-book-student-block.js","grade-book-table":"@haxtheweb/grade-book/lib/grade-book-table.js","letter-grade-picker":"@haxtheweb/grade-book/lib/letter-grade-picker.js","letter-grade":"@haxtheweb/grade-book/lib/letter-grade.js","grid-plate":"@haxtheweb/grid-plate/grid-plate.js","h-a-x":"@haxtheweb/h-a-x/h-a-x.js","h5p-element":"@haxtheweb/h5p-element/h5p-element.js","h5p-wrapped-element":"@haxtheweb/h5p-element/lib/h5p-wrapped-element.js","hal-9000":"@haxtheweb/hal-9000/hal-9000.js","hal-9000-ui":"@haxtheweb/hal-9000/lib/hal-9000-ui/hal-9000-ui.js","hax-body":"@haxtheweb/hax-body/hax-body.js","hax-app-picker":"@haxtheweb/hax-body/lib/hax-app-picker.js","hax-app-search":"@haxtheweb/hax-body/lib/hax-app-search.js","hax-app":"@haxtheweb/hax-body/lib/hax-app.js","hax-autoloader":"@haxtheweb/hax-body/lib/hax-autoloader.js","hax-cancel-dialog":"@haxtheweb/hax-body/lib/hax-cancel-dialog.js","hax-context-item-textop":"@haxtheweb/hax-body/lib/hax-context-item-textop.js","hax-context-item":"@haxtheweb/hax-body/lib/hax-context-item.js","hax-element-demo":"@haxtheweb/hax-body/lib/hax-element-demo.js","hax-export-dialog":"@haxtheweb/hax-body/lib/hax-export-dialog.js","hax-gizmo-browser":"@haxtheweb/hax-body/lib/hax-gizmo-browser.js","hax-map":"@haxtheweb/hax-body/lib/hax-map.js","hax-picker":"@haxtheweb/hax-body/lib/hax-picker.js","hax-plate-context":"@haxtheweb/hax-body/lib/hax-plate-context.js","hax-preferences-dialog":"@haxtheweb/hax-body/lib/hax-preferences-dialog.js","hax-stax-browser":"@haxtheweb/hax-body/lib/hax-stax-browser.js","hax-store":"@haxtheweb/hax-body/lib/hax-store.js","hax-text-editor-button":"@haxtheweb/hax-body/lib/hax-text-editor-button.js","hax-text-editor-paste-button":"@haxtheweb/hax-body/lib/hax-text-editor-paste-button.js","hax-text-editor-toolbar":"@haxtheweb/hax-body/lib/hax-text-editor-toolbar.js","hax-text-editor":"@haxtheweb/hax-body/lib/hax-text-editor.js","hax-toolbar-item":"@haxtheweb/hax-body/lib/hax-toolbar-item.js","hax-toolbar-menu":"@haxtheweb/hax-body/lib/hax-toolbar-menu.js","hax-toolbar":"@haxtheweb/hax-body/lib/hax-toolbar.js","hax-tray-button":"@haxtheweb/hax-body/lib/hax-tray-button.js","hax-tray-upload":"@haxtheweb/hax-body/lib/hax-tray-upload.js","hax-tray":"@haxtheweb/hax-body/lib/hax-tray.js","hax-ui-styles":"@haxtheweb/hax-body/lib/hax-ui-styles.js","hax-upload-field":"@haxtheweb/hax-body/lib/hax-upload-field.js","hax-view-source":"@haxtheweb/hax-body/lib/hax-view-source.js","hax-cloud":"@haxtheweb/hax-cloud/hax-cloud.js","hax-logo":"@haxtheweb/hax-logo/hax-logo.js","haxcms-backend-beaker":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-beaker.js","haxcms-backend-demo":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-demo.js","haxcms-backend-nodejs":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-nodejs.js","haxcms-backend-php":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-php.js","haxcms-backend-userfs":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-userfs.js","haxcms-darkmode-toggle":"@haxtheweb/haxcms-elements/lib/core/haxcms-darkmode-toggle.js","haxcms-editor-builder":"@haxtheweb/haxcms-elements/lib/core/haxcms-editor-builder.js","haxcms-outline-editor-dialog":"@haxtheweb/haxcms-elements/lib/core/haxcms-outline-editor-dialog.js","haxcms-share-dialog":"@haxtheweb/haxcms-elements/lib/core/haxcms-share-dialog.js","haxcms-site-builder":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-builder.js","haxcms-site-dashboard":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-dashboard.js","haxcms-site-editor-ui":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-editor-ui.js","haxcms-site-editor":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-editor.js","haxcms-site-insights":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-insights.js","haxcms-site-router":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-router.js","haxcms-site-store":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js","haxcms-toast":"@haxtheweb/haxcms-elements/lib/core/haxcms-toast.js","haxcms-button-add":"@haxtheweb/haxcms-elements/lib/core/micros/haxcms-button-add.js","haxcms-basic-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-basic-theme.js","haxcms-blank-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-blank-theme.js","haxcms-json-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-json-theme.js","haxcms-minimalist-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-minimalist-theme.js","haxcms-print-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-print-theme.js","haxcms-slide-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-slide-theme.js","haxcms-page-get-started":"@haxtheweb/haxcms-elements/lib/core/ui/haxcms-page-get-started.js","haxcms-dev-theme":"@haxtheweb/haxcms-elements/lib/development/haxcms-dev-theme.js","haxcms-theme-developer":"@haxtheweb/haxcms-elements/lib/development/haxcms-theme-developer.js","site-active-fields":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-fields.js","site-active-media-banner":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-media-banner.js","site-active-tags":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-tags.js","site-active-title":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-title.js","site-git-corner":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-git-corner.js","site-share-widget":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-share-widget.js","site-children-block":"@haxtheweb/haxcms-elements/lib/ui-components/blocks/site-children-block.js","site-outline-block":"@haxtheweb/haxcms-elements/lib/ui-components/blocks/site-outline-block.js","site-recent-content-block":"@haxtheweb/haxcms-elements/lib/ui-components/blocks/site-recent-content-block.js","site-drawer":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-drawer.js","site-footer":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-footer.js","site-modal":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-modal.js","site-region":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-region.js","active-when-visible":"@haxtheweb/haxcms-elements/lib/ui-components/magic/active-when-visible.js","site-ai-chat":"@haxtheweb/haxcms-elements/lib/ui-components/magic/site-ai-chat.js","site-collection-list":"@haxtheweb/haxcms-elements/lib/ui-components/magic/site-collection-list.js","site-view":"@haxtheweb/haxcms-elements/lib/ui-components/magic/site-view.js","site-breadcrumb":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-breadcrumb.js","site-dot-indicator":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-dot-indicator.js","site-menu-button":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-menu-button.js","site-menu-content":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-menu-content.js","site-menu":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-menu.js","site-top-menu":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-top-menu.js","site-query-menu-slice":"@haxtheweb/haxcms-elements/lib/ui-components/query/site-query-menu-slice.js","site-query":"@haxtheweb/haxcms-elements/lib/ui-components/query/site-query.js","site-render-query":"@haxtheweb/haxcms-elements/lib/ui-components/query/site-render-query.js","site-home-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-home-route.js","site-print-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-print-route.js","site-random-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-random-route.js","site-tags-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-tags-route.js","site-theme-style-guide-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-theme-style-guide-route.js","site-views-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-views-route.js","site-print-button":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-print-button.js","site-random-content":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-random-content.js","site-remote-content":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-remote-content.js","site-rss-button":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-rss-button.js","site-search":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-search.js","site-title":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-title.js","site-uuid-link":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-uuid-link.js","basic-template":"@haxtheweb/haxcms-elements/lib/ui-components/templates/basic-template.js","minimalist-template":"@haxtheweb/haxcms-elements/lib/ui-components/templates/minimalist-template.js","haxma-theme":"@haxtheweb/haxma-theme/haxma-theme.js","haxor-slevin":"@haxtheweb/haxor-slevin/haxor-slevin.js","hex-picker":"@haxtheweb/hex-picker/hex-picker.js","hexagon-loader":"@haxtheweb/hexagon-loader/hexagon-loader.js","hex-a-gon":"@haxtheweb/hexagon-loader/lib/hex-a-gon.js","html-block":"@haxtheweb/html-block/html-block.js","i18n-manager":"@haxtheweb/i18n-manager/i18n-manager.js","iframe-loader":"@haxtheweb/iframe-loader/iframe-loader.js","loading-indicator":"@haxtheweb/iframe-loader/lib/loading-indicator.js","image-compare-slider":"@haxtheweb/image-compare-slider/image-compare-slider.js","image-inspector":"@haxtheweb/image-inspector/image-inspector.js","img-pan-zoom":"@haxtheweb/img-pan-zoom/img-pan-zoom.js","img-loader":"@haxtheweb/img-pan-zoom/lib/img-loader.js","img-view-modal":"@haxtheweb/img-view-modal/img-view-modal.js","img-view-viewer":"@haxtheweb/img-view-modal/lib/img-view-viewer.js","inline-audio":"@haxtheweb/inline-audio/inline-audio.js","journey-theme":"@haxtheweb/journey-theme/journey-theme.js","journey-menu":"@haxtheweb/journey-theme/lib/journey-menu.js","journey-sidebar-theme":"@haxtheweb/journey-theme/lib/journey-sidebar-theme.js","journey-topbar-theme":"@haxtheweb/journey-theme/lib/journey-topbar-theme.js","json-editor":"@haxtheweb/json-editor/json-editor.js","json-outline-schema":"@haxtheweb/json-outline-schema/json-outline-schema.js","jos-render":"@haxtheweb/json-outline-schema/lib/jos-render.js","jwt-login":"@haxtheweb/jwt-login/jwt-login.js","la-tex":"@haxtheweb/la-tex/la-tex.js","lazy-image":"@haxtheweb/lazy-image-helpers/lazy-image-helpers.js","lazy-import-discover":"@haxtheweb/lazy-import-discover/lazy-import-discover.js","learn-two-theme":"@haxtheweb/learn-two-theme/learn-two-theme.js","lesson-overview":"@haxtheweb/lesson-overview/lesson-overview.js","lesson-highlight":"@haxtheweb/lesson-overview/lib/lesson-highlight.js","license-element":"@haxtheweb/license-element/license-element.js","lorem-data":"@haxtheweb/lorem-data/lorem-data.js","lrn-gitgraph":"@haxtheweb/lrn-gitgraph/lrn-gitgraph.js","lrn-math":"@haxtheweb/lrn-math/lrn-math.js","lrn-table":"@haxtheweb/lrn-table/lrn-table.js","lrn-vocab":"@haxtheweb/lrn-vocab/lrn-vocab.js","lrndesign-avatar":"@haxtheweb/lrndesign-avatar/lrndesign-avatar.js","lrndesign-bar":"@haxtheweb/lrndesign-chart/lib/lrndesign-bar.js","lrndesign-line":"@haxtheweb/lrndesign-chart/lib/lrndesign-line.js","lrndesign-pie":"@haxtheweb/lrndesign-chart/lib/lrndesign-pie.js","lrndesign-imagemap-hotspot":"@haxtheweb/lrndesign-imagemap/lib/lrndesign-imagemap-hotspot.js","lrndesign-imagemap":"@haxtheweb/lrndesign-imagemap/lrndesign-imagemap.js","lrndesign-sidenote":"@haxtheweb/lrndesign-sidenote/lrndesign-sidenote.js","lrndesign-timeline":"@haxtheweb/lrndesign-timeline/lrndesign-timeline.js","lrs-bridge-haxcms":"@haxtheweb/lrs-elements/lib/lrs-bridge-haxcms.js","lrs-bridge":"@haxtheweb/lrs-elements/lib/lrs-bridge.js","lrs-emitter":"@haxtheweb/lrs-elements/lib/lrs-emitter.js","lunr-search":"@haxtheweb/lunr-search/lunr-search.js","map-menu-builder":"@haxtheweb/map-menu/lib/map-menu-builder.js","map-menu-container":"@haxtheweb/map-menu/lib/map-menu-container.js","map-menu-header":"@haxtheweb/map-menu/lib/map-menu-header.js","map-menu-item":"@haxtheweb/map-menu/lib/map-menu-item.js","map-menu-submenu":"@haxtheweb/map-menu/lib/map-menu-submenu.js","map-menu":"@haxtheweb/map-menu/map-menu.js","mark-the-words":"@haxtheweb/mark-the-words/mark-the-words.js","matching-question":"@haxtheweb/matching-question/matching-question.js","md-block":"@haxtheweb/md-block/md-block.js","media-image":"@haxtheweb/media-image/media-image.js","media-quote":"@haxtheweb/media-quote/media-quote.js","meme-maker":"@haxtheweb/meme-maker/meme-maker.js","badge-sticker":"@haxtheweb/merit-badge/lib/badge-sticker.js","date-title":"@haxtheweb/merit-badge/lib/date-title.js","locked-badge":"@haxtheweb/merit-badge/lib/locked-badge.js","merit-badge":"@haxtheweb/merit-badge/merit-badge.js","micro-frontend-registry":"@haxtheweb/micro-frontend-registry/micro-frontend-registry.js","moar-sarcasm":"@haxtheweb/moar-sarcasm/moar-sarcasm.js","moment-element":"@haxtheweb/moment-element/moment-element.js","confetti-container":"@haxtheweb/multiple-choice/lib/confetti-container.js","short-answer-question":"@haxtheweb/multiple-choice/lib/short-answer-question.js","true-false-question":"@haxtheweb/multiple-choice/lib/true-false-question.js","multiple-choice":"@haxtheweb/multiple-choice/multiple-choice.js","midi-player":"@haxtheweb/music-player/lib/html-midi-player.js","music-player":"@haxtheweb/music-player/music-player.js","mutation-observer-import":"@haxtheweb/mutation-observer-import-mixin/mutation-observer-import-mixin.js","oer-schema":"@haxtheweb/oer-schema/oer-schema.js","outline-designer":"@haxtheweb/outline-designer/outline-designer.js","outline-player":"@haxtheweb/outline-player/outline-player.js","page-anchor":"@haxtheweb/page-break/lib/page-anchor.js","page-break-manager":"@haxtheweb/page-break/lib/page-break-manager.js","page-break-outline":"@haxtheweb/page-break/lib/page-break-outline.js","page-template":"@haxtheweb/page-break/lib/page-template.js","page-break":"@haxtheweb/page-break/page-break.js","page-contents-menu":"@haxtheweb/page-contents-menu/page-contents-menu.js","page-flag-comment":"@haxtheweb/page-flag/lib/page-flag-comment.js","page-flag":"@haxtheweb/page-flag/page-flag.js","page-scroll-position":"@haxtheweb/page-scroll-position/page-scroll-position.js","page-section":"@haxtheweb/page-section/page-section.js","paper-avatar":"@haxtheweb/paper-avatar/paper-avatar.js","paper-input-flagged":"@haxtheweb/paper-input-flagged/paper-input-flagged.js","paper-icon-step":"@haxtheweb/paper-stepper/lib/paper-icon-step.js","paper-icon-stepper":"@haxtheweb/paper-stepper/lib/paper-icon-stepper.js","paper-step":"@haxtheweb/paper-stepper/lib/paper-step.js","paper-stepper":"@haxtheweb/paper-stepper/paper-stepper.js","parallax-image":"@haxtheweb/parallax-image/parallax-image.js","pdf-browser-viewer":"@haxtheweb/pdf-browser-viewer/pdf-browser-viewer.js","person-testimonial":"@haxtheweb/person-testimonial/person-testimonial.js","place-holder":"@haxtheweb/place-holder/place-holder.js","play-list":"@haxtheweb/play-list/play-list.js","polaris-cta":"@haxtheweb/polaris-theme/lib/polaris-cta.js","polaris-flex-sidebar":"@haxtheweb/polaris-theme/lib/polaris-flex-sidebar.js","polaris-flex-theme":"@haxtheweb/polaris-theme/lib/polaris-flex-theme.js","polaris-invent-theme":"@haxtheweb/polaris-theme/lib/polaris-invent-theme.js","polaris-mark":"@haxtheweb/polaris-theme/lib/polaris-mark.js","polaris-story-card":"@haxtheweb/polaris-theme/lib/polaris-story-card.js","polaris-tile":"@haxtheweb/polaris-theme/lib/polaris-tile.js","polaris-theme":"@haxtheweb/polaris-theme/polaris-theme.js","portal-launcher":"@haxtheweb/portal-launcher/portal-launcher.js","post-card-photo":"@haxtheweb/post-card/lib/PostCardPhoto.js","post-card-postmark":"@haxtheweb/post-card/lib/PostCardPostmark.js","post-card-stamp":"@haxtheweb/post-card/lib/PostCardStamp.js","post-card":"@haxtheweb/post-card/post-card.js","pouch-db":"@haxtheweb/pouch-db/pouch-db.js","course-card":"@haxtheweb/product-card/lib/course-card.js","hax-element-card-list":"@haxtheweb/product-card/lib/hax-element-card-list.js","hax-element-list-selector":"@haxtheweb/product-card/lib/hax-element-list-selector.js","product-banner":"@haxtheweb/product-card/lib/product-banner.js","product-card":"@haxtheweb/product-card/product-card.js","product-glance":"@haxtheweb/product-glance/product-glance.js","product-offering":"@haxtheweb/product-offering/product-offering.js","progress-donut":"@haxtheweb/progress-donut/progress-donut.js","promise-progress-lite":"@haxtheweb/promise-progress/lib/promise-progress-lite.js","wc-preload-progress":"@haxtheweb/promise-progress/lib/wc-preload-progress.js","promise-progress":"@haxtheweb/promise-progress/promise-progress.js","qr-code":"@haxtheweb/q-r/lib/qr-code.js","q-r":"@haxtheweb/q-r/q-r.js","relative-heading-lite":"@haxtheweb/relative-heading/lib/relative-heading-lite.js","relative-heading-state-manager":"@haxtheweb/relative-heading/lib/relative-heading-state-manager.js","relative-heading":"@haxtheweb/relative-heading/relative-heading.js","performance-detect":"@haxtheweb/replace-tag/lib/PerformanceDetect.js","replace-tag":"@haxtheweb/replace-tag/replace-tag.js","responsive-grid-clear":"@haxtheweb/responsive-grid/lib/responsive-grid-clear.js","responsive-grid-col":"@haxtheweb/responsive-grid/lib/responsive-grid-col.js","responsive-grid-row":"@haxtheweb/responsive-grid/lib/responsive-grid-row.js","responsive-utility-element":"@haxtheweb/responsive-utility/lib/responsive-utility-element.js","responsive-utility":"@haxtheweb/responsive-utility/responsive-utility.js","retro-card":"@haxtheweb/retro-card/retro-card.js","rich-text-editor-button":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-button.js","rich-text-editor-emoji-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-emoji-picker.js","rich-text-editor-heading-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-heading-picker.js","rich-text-editor-icon-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-icon-picker.js","rich-text-editor-image":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-image.js","rich-text-editor-link":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-link.js","rich-text-editor-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-picker.js","rich-text-editor-prompt-button":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-prompt-button.js","rich-text-editor-source-code":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-source-code.js","rich-text-editor-symbol-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-symbol-picker.js","rich-text-editor-underline":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-underline.js","rich-text-editor-unlink":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-unlink.js","link-preview-card":"@haxtheweb/rich-text-editor/lib/open-apis/link-preview-card.js","rich-text-editor-clipboard":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-clipboard.js","rich-text-editor-highlight":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-highlight.js","rich-text-editor-prompt":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-prompt.js","rich-text-editor-source":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-source.js","rich-text-editor-breadcrumbs":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-breadcrumbs.js","rich-text-editor-toolbar-full":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-toolbar-full.js","rich-text-editor-toolbar-mini":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-toolbar-mini.js","rich-text-editor-toolbar":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-toolbar.js","rich-text-editor":"@haxtheweb/rich-text-editor/rich-text-editor.js","rpg-character":"@haxtheweb/rpg-character/rpg-character.js","runkit-embed":"@haxtheweb/runkit-embed/runkit-embed.js","screen-recorder":"@haxtheweb/screen-recorder/screen-recorder.js","scroll-button":"@haxtheweb/scroll-button/scroll-button.js","self-check":"@haxtheweb/self-check/self-check.js","shadow-style":"@haxtheweb/shadow-style/shadow-style.js","simple-autocomplete-text-trigger":"@haxtheweb/simple-autocomplete/lib/simple-autocomplete-text-trigger.js","simple-autocomplete":"@haxtheweb/simple-autocomplete/simple-autocomplete.js","simple-blog-card":"@haxtheweb/simple-blog-card/simple-blog-card.js","simple-blog-footer":"@haxtheweb/simple-blog/lib/simple-blog-footer.js","simple-blog-header":"@haxtheweb/simple-blog/lib/simple-blog-header.js","simple-blog-listing":"@haxtheweb/simple-blog/lib/simple-blog-listing.js","simple-blog-overview":"@haxtheweb/simple-blog/lib/simple-blog-overview.js","simple-blog-post":"@haxtheweb/simple-blog/lib/simple-blog-post.js","simple-blog":"@haxtheweb/simple-blog/simple-blog.js","simple-colors-shared-styles":"@haxtheweb/simple-colors-shared-styles/simple-colors-shared-styles.js","simple-colors-swatch-info":"@haxtheweb/simple-colors/lib/demo/simple-colors-swatch-info.js","simple-colors-swatches":"@haxtheweb/simple-colors/lib/demo/simple-colors-swatches.js","simple-colors-picker":"@haxtheweb/simple-colors/lib/simple-colors-picker.js","simple-colors-polymer":"@haxtheweb/simple-colors/lib/simple-colors-polymer.js","simple-colors":"@haxtheweb/simple-colors/simple-colors.js","simple-cta":"@haxtheweb/simple-cta/simple-cta.js","simple-datetime":"@haxtheweb/simple-datetime/simple-datetime.js","simple-emoji":"@haxtheweb/simple-emoji/simple-emoji.js","simple-fields-array":"@haxtheweb/simple-fields/lib/simple-fields-array.js","simple-fields-code":"@haxtheweb/simple-fields/lib/simple-fields-code.js","simple-fields-combo":"@haxtheweb/simple-fields/lib/simple-fields-combo.js","simple-fields-container":"@haxtheweb/simple-fields/lib/simple-fields-container.js","simple-fields-field":"@haxtheweb/simple-fields/lib/simple-fields-field.js","simple-fields-fieldset":"@haxtheweb/simple-fields/lib/simple-fields-fieldset.js","simple-fields-form-lite":"@haxtheweb/simple-fields/lib/simple-fields-form-lite.js","simple-fields-form":"@haxtheweb/simple-fields/lib/simple-fields-form.js","simple-fields-html-block":"@haxtheweb/simple-fields/lib/simple-fields-html-block.js","simple-fields-lite":"@haxtheweb/simple-fields/lib/simple-fields-lite.js","simple-fields-tab":"@haxtheweb/simple-fields/lib/simple-fields-tab.js","simple-fields-tabs":"@haxtheweb/simple-fields/lib/simple-fields-tabs.js","simple-fields-tag-list":"@haxtheweb/simple-fields/lib/simple-fields-tag-list.js","simple-fields-upload":"@haxtheweb/simple-fields/lib/simple-fields-upload.js","simple-fields-url-combo-item":"@haxtheweb/simple-fields/lib/simple-fields-url-combo-item.js","simple-fields-url-combo":"@haxtheweb/simple-fields/lib/simple-fields-url-combo.js","simple-tag-lite":"@haxtheweb/simple-fields/lib/simple-tag-lite.js","simple-tag":"@haxtheweb/simple-fields/lib/simple-tag.js","simple-tags":"@haxtheweb/simple-fields/lib/simple-tags.js","simple-fields":"@haxtheweb/simple-fields/simple-fields.js","simple-icon-picker":"@haxtheweb/simple-icon-picker/simple-icon-picker.js","simple-icon-button-lite":"@haxtheweb/simple-icon/lib/simple-icon-button-lite.js","simple-icon-button":"@haxtheweb/simple-icon/lib/simple-icon-button.js","simple-icon-lite":"@haxtheweb/simple-icon/lib/simple-icon-lite.js","simple-iconset-demo":"@haxtheweb/simple-icon/lib/simple-iconset-demo.js","simple-iconset":"@haxtheweb/simple-icon/lib/simple-iconset.js","simple-icon":"@haxtheweb/simple-icon/simple-icon.js","simple-img":"@haxtheweb/simple-img/simple-img.js","simple-camera-snap":"@haxtheweb/simple-login/lib/simple-camera-snap.js","simple-login-avatar":"@haxtheweb/simple-login/lib/simple-login-avatar.js","simple-login-camera":"@haxtheweb/simple-login/lib/simple-login-camera.js","simple-login":"@haxtheweb/simple-login/simple-login.js","simple-modal-template":"@haxtheweb/simple-modal/lib/simple-modal-template.js","simple-modal":"@haxtheweb/simple-modal/simple-modal.js","simple-emoji-picker":"@haxtheweb/simple-picker/lib/simple-emoji-picker.js","simple-picker-option":"@haxtheweb/simple-picker/lib/simple-picker-option.js","simple-symbol-picker":"@haxtheweb/simple-picker/lib/simple-symbol-picker.js","simple-picker":"@haxtheweb/simple-picker/simple-picker.js","simple-popover-manager":"@haxtheweb/simple-popover/lib/simple-popover-manager.js","simple-popover-selection":"@haxtheweb/simple-popover/lib/simple-popover-selection.js","simple-tour":"@haxtheweb/simple-popover/lib/simple-tour.js","simple-popover":"@haxtheweb/simple-popover/simple-popover.js","simple-progress":"@haxtheweb/simple-progress/simple-progress.js","simple-range-input":"@haxtheweb/simple-range-input/simple-range-input.js","simple-search-content":"@haxtheweb/simple-search/lib/simple-search-content.js","simple-search-match":"@haxtheweb/simple-search/lib/simple-search-match.js","simple-search":"@haxtheweb/simple-search/simple-search.js","simple-toast-el":"@haxtheweb/simple-toast/lib/simple-toast-el.js","simple-toast":"@haxtheweb/simple-toast/simple-toast.js","simple-button-grid":"@haxtheweb/simple-toolbar/lib/simple-button-grid.js","simple-toolbar-button-group":"@haxtheweb/simple-toolbar/lib/simple-toolbar-button-group.js","simple-toolbar-button":"@haxtheweb/simple-toolbar/lib/simple-toolbar-button.js","simple-toolbar-field":"@haxtheweb/simple-toolbar/lib/simple-toolbar-field.js","simple-toolbar-menu-item":"@haxtheweb/simple-toolbar/lib/simple-toolbar-menu-item.js","simple-toolbar-menu":"@haxtheweb/simple-toolbar/lib/simple-toolbar-menu.js","simple-toolbar-more-button":"@haxtheweb/simple-toolbar/lib/simple-toolbar-more-button.js","simple-toolbar":"@haxtheweb/simple-toolbar/simple-toolbar.js","simple-tooltip":"@haxtheweb/simple-tooltip/simple-tooltip.js","social-share-link":"@haxtheweb/social-share-link/social-share-link.js","sorting-option":"@haxtheweb/sorting-question/lib/sorting-option.js","sorting-question":"@haxtheweb/sorting-question/sorting-question.js","spacebook-theme":"@haxtheweb/spacebook-theme/spacebook-theme.js","spotify-embed":"@haxtheweb/spotify-embed/spotify-embed.js","star-rating":"@haxtheweb/star-rating/star-rating.js","stop-note":"@haxtheweb/stop-note/stop-note.js","super-daemon-row":"@haxtheweb/super-daemon/lib/super-daemon-row.js","super-daemon-search":"@haxtheweb/super-daemon/lib/super-daemon-search.js","super-daemon-toast":"@haxtheweb/super-daemon/lib/super-daemon-toast.js","super-daemon-ui":"@haxtheweb/super-daemon/lib/super-daemon-ui.js","super-daemon":"@haxtheweb/super-daemon/super-daemon.js","tagging-question":"@haxtheweb/tagging-question/tagging-question.js","terrible-best-themes":"@haxtheweb/terrible-themes/lib/terrible-best-themes.js","terrible-outlet-themes":"@haxtheweb/terrible-themes/lib/terrible-outlet-themes.js","terrible-productionz-themes":"@haxtheweb/terrible-themes/lib/terrible-productionz-themes.js","terrible-resume-themes":"@haxtheweb/terrible-themes/lib/terrible-resume-themes.js","terrible-themes":"@haxtheweb/terrible-themes/terrible-themes.js","training-button":"@haxtheweb/training-theme/lib/training-button.js","training-top":"@haxtheweb/training-theme/lib/training-top.js","training-theme":"@haxtheweb/training-theme/training-theme.js","twitter-embed-vanilla":"@haxtheweb/twitter-embed/lib/twitter-embed-vanilla.js","twitter-embed":"@haxtheweb/twitter-embed/twitter-embed.js","type-writer":"@haxtheweb/type-writer/type-writer.js","un-sdg":"@haxtheweb/un-sdg/un-sdg.js","undo-manager":"@haxtheweb/undo-manager/undo-manager.js","unity-webgl":"@haxtheweb/unity-webgl/unity-webgl.js","user-action":"@haxtheweb/user-action/user-action.js","user-scaffold":"@haxtheweb/user-scaffold/user-scaffold.js","demo-snippet":"@haxtheweb/utils/lib/demo-snippet.js","lecture-anchor":"@haxtheweb/video-player/lib/lecture-anchor.js","lecture-player":"@haxtheweb/video-player/lib/lecture-player.js","video-player":"@haxtheweb/video-player/video-player.js","vocab-term":"@haxtheweb/vocab-term/vocab-term.js","voice-recorder":"@haxtheweb/voice-recorder/voice-recorder.js","wc-registry":"@haxtheweb/wc-autoload/wc-autoload.js","web-container-doc-player":"@haxtheweb/web-container/lib/web-container-doc-player.js","web-container-wc-registry-docs":"@haxtheweb/web-container/lib/web-container-wc-registry-docs.js","web-container":"@haxtheweb/web-container/web-container.js","wikipedia-query":"@haxtheweb/wikipedia-query/wikipedia-query.js","word-count":"@haxtheweb/word-count/word-count.js","wysiwyg-hax":"@haxtheweb/wysiwyg-hax/wysiwyg-hax.js","lit-virtualizer":"@lit-labs/virtualizer/lit-virtualizer.js","app-box":"@polymer/app-layout/app-box/app-box.js","app-drawer-layout":"@polymer/app-layout/app-drawer-layout/app-drawer-layout.js","app-drawer":"@polymer/app-layout/app-drawer/app-drawer.js","app-header-layout":"@polymer/app-layout/app-header-layout/app-header-layout.js","app-header":"@polymer/app-layout/app-header/app-header.js","x-container":"@polymer/app-layout/app-scroll-effects/test/x-container.js","app-toolbar":"@polymer/app-layout/app-toolbar/app-toolbar.js","iron-a11y-announcer":"@polymer/iron-a11y-announcer/iron-a11y-announcer.js","iron-a11y-keys":"@polymer/iron-a11y-keys/iron-a11y-keys.js","iron-ajax":"@polymer/iron-ajax/iron-ajax.js","iron-request":"@polymer/iron-ajax/iron-request.js","iron-autogrow-textarea":"@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js","iron-component-page":"@polymer/iron-component-page/iron-component-page.js","iron-doc-api":"@polymer/iron-doc-viewer/iron-doc-api.js","iron-doc-behavior":"@polymer/iron-doc-viewer/iron-doc-behavior.js","iron-doc-class":"@polymer/iron-doc-viewer/iron-doc-class.js","iron-doc-demo":"@polymer/iron-doc-viewer/iron-doc-demo.js","iron-doc-element":"@polymer/iron-doc-viewer/iron-doc-element.js","iron-doc-function":"@polymer/iron-doc-viewer/iron-doc-function.js","iron-doc-hide-bar":"@polymer/iron-doc-viewer/iron-doc-hide-bar.js","iron-doc-mixin":"@polymer/iron-doc-viewer/iron-doc-mixin.js","iron-doc-module":"@polymer/iron-doc-viewer/iron-doc-module.js","iron-doc-namespace":"@polymer/iron-doc-viewer/iron-doc-namespace.js","iron-doc-nav":"@polymer/iron-doc-viewer/iron-doc-nav.js","iron-doc-property":"@polymer/iron-doc-viewer/iron-doc-property.js","iron-doc-summary":"@polymer/iron-doc-viewer/iron-doc-summary.js","iron-doc-viewer":"@polymer/iron-doc-viewer/iron-doc-viewer.js","iron-icon":"@polymer/iron-icon/iron-icon.js","iron-iconset-svg":"@polymer/iron-iconset-svg/iron-iconset-svg.js","iron-image":"@polymer/iron-image/iron-image.js","iron-input":"@polymer/iron-input/iron-input.js","iron-list":"@polymer/iron-list/iron-list.js","iron-location":"@polymer/iron-location/iron-location.js","iron-query-params":"@polymer/iron-location/iron-query-params.js","iron-media-query":"@polymer/iron-media-query/iron-media-query.js","iron-meta":"@polymer/iron-meta/iron-meta.js","iron-overlay-backdrop":"@polymer/iron-overlay-behavior/iron-overlay-backdrop.js","iron-pages":"@polymer/iron-pages/iron-pages.js","x-app":"@polymer/iron-resizable-behavior/demo/src/x-app.js","x-puck":"@polymer/iron-resizable-behavior/demo/src/x-puck.js","iron-selector":"@polymer/iron-selector/iron-selector.js","marked-element":"@polymer/marked-element/marked-element.js","cascaded-animation":"@polymer/neon-animation/animations/cascaded-animation.js","fade-in-animation":"@polymer/neon-animation/animations/fade-in-animation.js","fade-out-animation":"@polymer/neon-animation/animations/fade-out-animation.js","hero-animation":"@polymer/neon-animation/animations/hero-animation.js","opaque-animation":"@polymer/neon-animation/animations/opaque-animation.js","reverse-ripple-animation":"@polymer/neon-animation/animations/reverse-ripple-animation.js","ripple-animation":"@polymer/neon-animation/animations/ripple-animation.js","scale-down-animation":"@polymer/neon-animation/animations/scale-down-animation.js","scale-up-animation":"@polymer/neon-animation/animations/scale-up-animation.js","slide-down-animation":"@polymer/neon-animation/animations/slide-down-animation.js","slide-from-bottom-animation":"@polymer/neon-animation/animations/slide-from-bottom-animation.js","slide-from-left-animation":"@polymer/neon-animation/animations/slide-from-left-animation.js","slide-from-right-animation":"@polymer/neon-animation/animations/slide-from-right-animation.js","slide-from-top-animation":"@polymer/neon-animation/animations/slide-from-top-animation.js","slide-left-animation":"@polymer/neon-animation/animations/slide-left-animation.js","slide-right-animation":"@polymer/neon-animation/animations/slide-right-animation.js","slide-up-animation":"@polymer/neon-animation/animations/slide-up-animation.js","transform-animation":"@polymer/neon-animation/animations/transform-animation.js","x-card":"@polymer/neon-animation/demo/card/x-card.js","x-cards-list":"@polymer/neon-animation/demo/card/x-cards-list.js","my-animatable":"@polymer/neon-animation/demo/doc/my-animatable.js","my-dialog":"@polymer/neon-animation/demo/doc/my-dialog.js","animated-dropdown":"@polymer/neon-animation/demo/dropdown/animated-dropdown.js","animated-grid":"@polymer/neon-animation/demo/load/animated-grid.js","fullsize-page-with-card":"@polymer/neon-animation/demo/grid/fullsize-page-with-card.js","full-view":"@polymer/neon-animation/demo/list/full-view.js","list-demo":"@polymer/neon-animation/demo/list/list-demo.js","list-view":"@polymer/neon-animation/demo/list/list-view.js","full-page":"@polymer/neon-animation/demo/load/full-page.js","circles-page":"@polymer/neon-animation/demo/tiles/circles-page.js","squares-page":"@polymer/neon-animation/demo/tiles/squares-page.js","neon-animatable":"@polymer/neon-animation/neon-animatable.js","neon-animated-pages":"@polymer/neon-animation/neon-animated-pages.js","paper-card":"@polymer/paper-card/paper-card.js","paper-dialog-scrollable":"@polymer/paper-dialog-scrollable/paper-dialog-scrollable.js","paper-dialog":"@polymer/paper-dialog/paper-dialog.js","paper-icon-button-light":"@polymer/paper-icon-button/paper-icon-button-light.js","paper-icon-button":"@polymer/paper-icon-button/paper-icon-button.js","paper-input-char-counter":"@polymer/paper-input/paper-input-char-counter.js","paper-input-container":"@polymer/paper-input/paper-input-container.js","paper-input-error":"@polymer/paper-input/paper-input-error.js","paper-input":"@polymer/paper-input/paper-input.js","paper-textarea":"@polymer/paper-input/paper-textarea.js","paper-icon-item":"@polymer/paper-item/paper-icon-item.js","paper-item-body":"@polymer/paper-item/paper-item-body.js","paper-item":"@polymer/paper-item/paper-item.js","paper-progress":"@polymer/paper-progress/paper-progress.js","paper-ripple":"@polymer/paper-ripple/paper-ripple.js","paper-spinner-lite":"@polymer/paper-spinner/paper-spinner-lite.js","paper-spinner":"@polymer/paper-spinner/paper-spinner.js","paper-toast":"@polymer/paper-toast/paper-toast.js","array-selector":"@polymer/polymer/lib/elements/array-selector.js","custom-style":"@polymer/polymer/lib/elements/custom-style.js","dom-bind":"@polymer/polymer/lib/elements/dom-bind.js","dom-if":"@polymer/polymer/lib/elements/dom-if.js","dom-module":"@polymer/polymer/lib/elements/dom-module.js","dom-repeat":"@polymer/polymer/lib/elements/dom-repeat.js","prism-highlighter":"@polymer/prism-element/prism-highlighter.js","vaadin-button":"@vaadin/vaadin-button/src/vaadin-button.js","vaadin-checkbox-group":"@vaadin/vaadin-checkbox/src/vaadin-checkbox-group.js","vaadin-checkbox":"@vaadin/vaadin-checkbox/src/vaadin-checkbox.js","vaadin-grid-column-group":"@vaadin/vaadin-grid/src/vaadin-grid-column-group.js","vaadin-grid-column":"@vaadin/vaadin-grid/src/vaadin-grid-column.js","vaadin-grid-filter-column":"@vaadin/vaadin-grid/src/vaadin-grid-filter-column.js","vaadin-grid-filter":"@vaadin/vaadin-grid/src/vaadin-grid-filter.js","vaadin-grid-scroller":"@vaadin/vaadin-grid/src/vaadin-grid-scroller.js","vaadin-grid-selection-column":"@vaadin/vaadin-grid/src/vaadin-grid-selection-column.js","vaadin-grid-sort-column":"@vaadin/vaadin-grid/src/vaadin-grid-sort-column.js","vaadin-grid-sorter":"@vaadin/vaadin-grid/src/vaadin-grid-sorter.js","vaadin-grid-templatizer":"@vaadin/vaadin-grid/src/vaadin-grid-templatizer.js","vaadin-grid-tree-column":"@vaadin/vaadin-grid/src/vaadin-grid-tree-column.js","vaadin-grid-tree-toggle":"@vaadin/vaadin-grid/src/vaadin-grid-tree-toggle.js","vaadin-grid":"@vaadin/vaadin-grid/src/vaadin-grid.js","vaadin-lumo-styles":"@vaadin/vaadin-lumo-styles/version.js","vaadin-material-styles":"@vaadin/vaadin-material-styles/version.js","vaadin-progress-bar":"@vaadin/vaadin-progress-bar/src/vaadin-progress-bar.js","vaadin-email-field":"@vaadin/vaadin-text-field/src/vaadin-email-field.js","vaadin-integer-field":"@vaadin/vaadin-text-field/src/vaadin-integer-field.js","vaadin-number-field":"@vaadin/vaadin-text-field/src/vaadin-number-field.js","vaadin-password-field":"@vaadin/vaadin-text-field/src/vaadin-password-field.js","vaadin-text-area":"@vaadin/vaadin-text-field/src/vaadin-text-area.js","vaadin-text-field":"@vaadin/vaadin-text-field/src/vaadin-text-field.js","vaadin-upload-file":"@vaadin/vaadin-upload/src/vaadin-upload-file.js","vaadin-upload":"@vaadin/vaadin-upload/src/vaadin-upload.js","scrollable-component":"scrollable-component/index.js","web-dialog":"web-dialog/web-dialog.js"} \ No newline at end of file +{"focus-trap":"@a11y/focus-trap/focus-trap.js","local-time":"@github/time-elements/dist/local-time-element.js","relative-time":"@github/time-elements/dist/relative-time-element.js","time-ago":"@github/time-elements/dist/time-ago-element.js","time-until":"@github/time-elements/dist/time-until-element.js","model-viewer":"@google/model-viewer/dist/model-viewer.js","a11y-carousel":"@haxtheweb/a11y-carousel/a11y-carousel.js","a11y-carousel-button":"@haxtheweb/a11y-carousel/lib/a11y-carousel-button.js","a11y-collapse":"@haxtheweb/a11y-collapse/a11y-collapse.js","a11y-collapse-group":"@haxtheweb/a11y-collapse/lib/a11y-collapse-group.js","a11y-compare-image":"@haxtheweb/a11y-compare-image/a11y-compare-image.js","a11y-details":"@haxtheweb/a11y-details/a11y-details.js","a11y-figure":"@haxtheweb/a11y-figure/a11y-figure.js","a11y-gif-player":"@haxtheweb/a11y-gif-player/a11y-gif-player.js","a11y-media-player":"@haxtheweb/a11y-media-player/a11y-media-player.js","a11y-media-button":"@haxtheweb/a11y-media-player/lib/a11y-media-button.js","a11y-media-play-button":"@haxtheweb/a11y-media-player/lib/a11y-media-play-button.js","a11y-media-state-manager":"@haxtheweb/a11y-media-player/lib/a11y-media-state-manager.js","a11y-media-transcript-cue":"@haxtheweb/a11y-media-player/lib/a11y-media-transcript-cue.js","a11y-media-youtube":"@haxtheweb/a11y-media-player/lib/a11y-media-youtube.js","a11y-menu-button":"@haxtheweb/a11y-menu-button/a11y-menu-button.js","a11y-menu-button-item":"@haxtheweb/a11y-menu-button/lib/a11y-menu-button-item.js","a11y-tabs":"@haxtheweb/a11y-tabs/a11y-tabs.js","a11y-tab":"@haxtheweb/a11y-tabs/lib/a11y-tab.js","absolute-position-behavior":"@haxtheweb/absolute-position-behavior/absolute-position-behavior.js","absolute-position-state-manager":"@haxtheweb/absolute-position-behavior/lib/absolute-position-state-manager.js","accent-card":"@haxtheweb/accent-card/accent-card.js","aframe-player":"@haxtheweb/aframe-player/aframe-player.js","air-horn":"@haxtheweb/air-horn/air-horn.js","app-hax":"@haxtheweb/app-hax/app-hax.js","app-hax-theme":"@haxtheweb/app-hax/lib/app-hax-theme.js","random-word":"@haxtheweb/app-hax/lib/random-word/random-word.js","rpg-character-toast":"@haxtheweb/haxcms-elements/lib/core/ui/rpg-character-toast/rpg-character-toast.js","app-hax-button":"@haxtheweb/app-hax/lib/v2/app-hax-button.js","app-hax-hat-progress":"@haxtheweb/app-hax/lib/v2/app-hax-hat-progress.js","app-hax-label":"@haxtheweb/app-hax/lib/v2/app-hax-label.js","app-hax-search-bar":"@haxtheweb/app-hax/lib/v2/app-hax-search-bar.js","app-hax-search-results":"@haxtheweb/app-hax/lib/v2/app-hax-search-results.js","app-hax-site-bar":"@haxtheweb/app-hax/lib/v2/app-hax-site-bar.js","app-hax-site-button":"@haxtheweb/app-hax/lib/v2/app-hax-site-button.js","app-hax-site-details":"@haxtheweb/app-hax/lib/v2/app-hax-site-details.js","app-hax-site-login":"@haxtheweb/app-hax/lib/v2/app-hax-site-login.js","app-hax-steps":"@haxtheweb/app-hax/lib/v2/app-hax-steps.js","app-hax-toast":"@haxtheweb/app-hax/lib/v2/app-hax-toast.js","app-hax-top-bar":"@haxtheweb/haxcms-elements/lib/core/ui/app-hax-top-bar.js","app-hax-user-menu-button":"@haxtheweb/haxcms-elements/lib/core/ui/app-hax-user-menu-button.js","app-hax-user-menu":"@haxtheweb/haxcms-elements/lib/core/ui/app-hax-user-menu.js","app-hax-wired-toggle":"@haxtheweb/app-hax/lib/v2/app-hax-wired-toggle.js","app-hax-backend-api":"@haxtheweb/app-hax/lib/v2/AppHaxBackendAPI.js","app-hax-router":"@haxtheweb/app-hax/lib/v2/AppHaxRouter.js","app-hax-confirmation-modal":"@haxtheweb/app-hax/lib/v2/app-hax-confirmation-modal.js","app-hax-filter-tag":"@haxtheweb/app-hax/lib/v2/app-hax-filter-tag.js","app-hax-scroll-button":"@haxtheweb/app-hax/lib/v2/app-hax-scroll-button.js","app-hax-simple-hat-progress":"@haxtheweb/app-hax/lib/v2/app-hax-simple-hat-progress.js","app-hax-site-creation-modal":"@haxtheweb/app-hax/lib/v2/app-hax-site-creation-modal.js","app-hax-use-case-filter":"@haxtheweb/app-hax/lib/v2/app-hax-use-case-filter.js","app-hax-use-case":"@haxtheweb/app-hax/lib/v2/app-hax-use-case.js","app-hax-user-access-modal":"@haxtheweb/app-hax/lib/v2/app-hax-user-access-modal.js","wired-darkmode-toggle":"@haxtheweb/haxcms-elements/lib/core/ui/wired-darkmode-toggle/wired-darkmode-toggle.js","audio-player":"@haxtheweb/audio-player/audio-player.js","author-card":"@haxtheweb/author-card/author-card.js","awesome-explosion":"@haxtheweb/awesome-explosion/awesome-explosion.js","b-r":"@haxtheweb/b-r/b-r.js","barcode-reader":"@haxtheweb/barcode-reader/barcode-reader.js","beaker-broker":"@haxtheweb/beaker-broker/beaker-broker.js","bootstrap-theme":"@haxtheweb/bootstrap-theme/bootstrap-theme.js","bootstrap-breadcrumb":"@haxtheweb/bootstrap-theme/lib/BootstrapBreadcrumb.js","bootstrap-footer":"@haxtheweb/bootstrap-theme/lib/BootstrapFooter.js","bootstrap-search":"@haxtheweb/bootstrap-theme/lib/BootstrapSearch.js","chartist-render":"@haxtheweb/chartist-render/chartist-render.js","chat-agent":"@haxtheweb/chat-agent/chat-agent.js","chat-button":"@haxtheweb/chat-agent/lib/chat-button.js","chat-control-bar":"@haxtheweb/chat-agent/lib/chat-control-bar.js","chat-developer-panel":"@haxtheweb/chat-agent/lib/chat-developer-panel.js","chat-input":"@haxtheweb/chat-agent/lib/chat-input.js","chat-interface":"@haxtheweb/chat-agent/lib/chat-interface.js","chat-message":"@haxtheweb/chat-agent/lib/chat-message.js","chat-suggestion":"@haxtheweb/chat-agent/lib/chat-suggestion.js","check-it-out":"@haxtheweb/check-it-out/check-it-out.js","citation-element":"@haxtheweb/citation-element/citation-element.js","clean-one":"@haxtheweb/clean-one/clean-one.js","clean-one-search-box":"@haxtheweb/clean-one/lib/clean-one-search-box.js","clean-portfolio-theme":"@haxtheweb/clean-portfolio-theme/clean-portfolio-theme.js","clean-two":"@haxtheweb/clean-two/clean-two.js","cms-hax":"@haxtheweb/cms-hax/cms-hax.js","cms-block":"@haxtheweb/cms-hax/lib/cms-block.js","cms-entity":"@haxtheweb/cms-hax/lib/cms-entity.js","cms-token":"@haxtheweb/cms-hax/lib/cms-token.js","cms-views":"@haxtheweb/cms-hax/lib/cms-views.js","code-editor":"@haxtheweb/code-editor/code-editor.js","code-pen-button":"@haxtheweb/code-editor/lib/code-pen-button.js","monaco-element":"@haxtheweb/code-editor/lib/monaco-element/monaco-element.js","code-sample":"@haxtheweb/code-sample/code-sample.js","collection-list":"@haxtheweb/collection-list/collection-list.js","collection-item":"@haxtheweb/collection-list/lib/collection-item.js","collection-row":"@haxtheweb/collection-list/lib/collection-row.js","collections-theme-banner":"@haxtheweb/collection-list/lib/collections-theme-banner.js","collections-theme":"@haxtheweb/collection-list/lib/collections-theme.js","count-up":"@haxtheweb/count-up/count-up.js","course-design":"@haxtheweb/course-design/course-design.js","activity-box":"@haxtheweb/course-design/lib/activity-box.js","block-quote":"@haxtheweb/course-design/lib/block-quote.js","course-intro-header":"@haxtheweb/course-design/lib/course-intro-header.js","course-intro-lesson-plan":"@haxtheweb/course-design/lib/course-intro-lesson-plan.js","course-intro-lesson-plans":"@haxtheweb/course-design/lib/course-intro-lesson-plans.js","course-intro":"@haxtheweb/course-design/lib/course-intro.js","ebook-button":"@haxtheweb/course-design/lib/ebook-button.js","learning-component":"@haxtheweb/course-design/lib/learning-component.js","lrn-h5p":"@haxtheweb/course-design/lib/lrn-h5p.js","responsive-iframe":"@haxtheweb/course-design/lib/responsive-iframe.js","worksheet-download":"@haxtheweb/course-design/lib/worksheet-download.js","course-model":"@haxtheweb/course-model/course-model.js","model-info":"@haxtheweb/course-model/lib/model-info.js","model-option":"@haxtheweb/course-model/lib/model-option.js","csv-render":"@haxtheweb/csv-render/csv-render.js","d-d-d":"@haxtheweb/d-d-d/d-d-d.js","ddd-brochure-theme":"@haxtheweb/d-d-d/lib/ddd-brochure-theme.js","ddd-card":"@haxtheweb/d-d-d/lib/ddd-card.js","ddd-steps-list-item":"@haxtheweb/d-d-d/lib/ddd-steps-list-item.js","ddd-steps-list":"@haxtheweb/d-d-d/lib/ddd-steps-list.js","design-system":"@haxtheweb/d-d-d/lib/DesignSystemManager.js","mini-map":"@haxtheweb/d-d-d/lib/mini-map.js","d-d-docs":"@haxtheweb/d-d-docs/d-d-docs.js","data-viz":"@haxtheweb/data-viz/data-viz.js","date-card":"@haxtheweb/date-card/date-card.js","date-chip":"@haxtheweb/date-card/lib/date-chip.js","demo-snippet":"@haxtheweb/demo-snippet/demo-snippet.js","discord-embed":"@haxtheweb/discord-embed/discord-embed.js","disqus-embed":"@haxtheweb/disqus-embed/disqus-embed.js","haxcms-site-disqus":"@haxtheweb/disqus-embed/lib/haxcms-site-disqus.js","documentation-player":"@haxtheweb/documentation-player/documentation-player.js","dynamic-import-registry":"@haxtheweb/dynamic-import-registry/dynamic-import-registry.js","editable-outline":"@haxtheweb/editable-outline/editable-outline.js","editable-table":"@haxtheweb/editable-table/editable-table.js","editable-table-display":"@haxtheweb/editable-table/lib/editable-table-display.js","editable-table-edit":"@haxtheweb/editable-table/lib/editable-table-edit.js","editable-table-editor-rowcol":"@haxtheweb/editable-table/lib/editable-table-editor-rowcol.js","editable-table-filter":"@haxtheweb/editable-table/lib/editable-table-filter.js","editable-table-sort":"@haxtheweb/editable-table/lib/editable-table-sort.js","elmsln-loading":"@haxtheweb/elmsln-loading/elmsln-loading.js","enhanced-text":"@haxtheweb/enhanced-text/enhanced-text.js","event-badge":"@haxtheweb/event-badge/event-badge.js","example-hax-element":"@haxtheweb/example-hax-element/example-hax-element.js","example-haxcms-theme":"@haxtheweb/example-haxcms-theme/example-haxcms-theme.js","figure-label":"@haxtheweb/figure-label/figure-label.js","file-system-broker":"@haxtheweb/file-system-broker/file-system-broker.js","docx-file-system-broker":"@haxtheweb/file-system-broker/lib/docx-file-system-broker.js","xlsx-file-system-broker":"@haxtheweb/file-system-broker/lib/xlsx-file-system-broker.js","fill-in-the-blanks":"@haxtheweb/fill-in-the-blanks/fill-in-the-blanks.js","flash-card":"@haxtheweb/flash-card/flash-card.js","flash-card-answer-box":"@haxtheweb/flash-card/lib/flash-card-answer-box.js","flash-card-image-prompt":"@haxtheweb/flash-card/lib/flash-card-prompt-img.js","flash-card-set":"@haxtheweb/flash-card/lib/flash-card-set.js","fluid-type":"@haxtheweb/fluid-type/fluid-type.js","full-width-image":"@haxtheweb/full-width-image/full-width-image.js","fullscreen-behaviors":"@haxtheweb/fullscreen-behaviors/fullscreen-behaviors.js","future-terminal-text":"@haxtheweb/future-terminal-text/future-terminal-text.js","future-terminal-text-lite":"@haxtheweb/future-terminal-text/lib/future-terminal-text-lite.js","git-corner":"@haxtheweb/git-corner/git-corner.js","github-preview":"@haxtheweb/github-preview/github-preview.js","github-rpg-contributors":"@haxtheweb/github-preview/lib/github-rpg-contributors.js","wc-markdown":"@haxtheweb/github-preview/lib/wc-markdown.js","glossy-portfolio-theme":"@haxtheweb/glossy-portfolio-theme/glossy-portfolio-theme.js","glossy-portfolio-about":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-about.js","glossy-portfolio-breadcrumb":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-breadcrumb.js","glossy-portfolio-card":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-card.js","glossy-portfolio-footer":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-footer.js","glossy-portfolio-grid":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-grid.js","glossy-portfolio-header":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-header.js","glossy-portfolio-home":"@haxtheweb/glossy-portfolio-theme/lib/glossy-portfolio-home.js","grade-book":"@haxtheweb/grade-book/grade-book.js","grade-book-lite":"@haxtheweb/grade-book/lib/grade-book-lite.js","grade-book-pop-up":"@haxtheweb/grade-book/lib/grade-book-pop-up.js","grade-book-store":"@haxtheweb/grade-book/lib/grade-book-store.js","grade-book-student-block":"@haxtheweb/grade-book/lib/grade-book-student-block.js","grade-book-table":"@haxtheweb/grade-book/lib/grade-book-table.js","letter-grade-picker":"@haxtheweb/grade-book/lib/letter-grade-picker.js","letter-grade":"@haxtheweb/grade-book/lib/letter-grade.js","grid-plate":"@haxtheweb/grid-plate/grid-plate.js","h-a-x":"@haxtheweb/h-a-x/h-a-x.js","h5p-element":"@haxtheweb/h5p-element/h5p-element.js","h5p-wrapped-element":"@haxtheweb/h5p-element/lib/h5p-wrapped-element.js","hal-9000":"@haxtheweb/hal-9000/hal-9000.js","hal-9000-ui":"@haxtheweb/hal-9000/lib/hal-9000-ui/hal-9000-ui.js","hax-body":"@haxtheweb/hax-body/hax-body.js","hax-text-editor-alignment-picker":"@haxtheweb/hax-body/lib/buttons/hax-text-editor-alignment-picker.js","hax-app-picker":"@haxtheweb/hax-body/lib/hax-app-picker.js","hax-app-search":"@haxtheweb/hax-body/lib/hax-app-search.js","hax-app":"@haxtheweb/hax-body/lib/hax-app.js","hax-autoloader":"@haxtheweb/hax-body/lib/hax-autoloader.js","hax-cancel-dialog":"@haxtheweb/hax-body/lib/hax-cancel-dialog.js","hax-context-item-textop":"@haxtheweb/hax-body/lib/hax-context-item-textop.js","hax-context-item":"@haxtheweb/hax-body/lib/hax-context-item.js","hax-element-demo":"@haxtheweb/hax-body/lib/hax-element-demo.js","hax-export-dialog":"@haxtheweb/hax-body/lib/hax-export-dialog.js","hax-gizmo-browser":"@haxtheweb/hax-body/lib/hax-gizmo-browser.js","hax-map":"@haxtheweb/hax-body/lib/hax-map.js","hax-picker":"@haxtheweb/hax-body/lib/hax-picker.js","hax-plate-context":"@haxtheweb/hax-body/lib/hax-plate-context.js","hax-preferences-dialog":"@haxtheweb/hax-body/lib/hax-preferences-dialog.js","hax-stax-browser":"@haxtheweb/hax-body/lib/hax-stax-browser.js","hax-store":"@haxtheweb/hax-body/lib/hax-store.js","hax-text-editor-button":"@haxtheweb/hax-body/lib/hax-text-editor-button.js","hax-text-editor-paste-button":"@haxtheweb/hax-body/lib/hax-text-editor-paste-button.js","hax-text-editor-toolbar":"@haxtheweb/hax-body/lib/hax-text-editor-toolbar.js","hax-text-editor":"@haxtheweb/hax-body/lib/hax-text-editor.js","hax-toolbar-item":"@haxtheweb/hax-body/lib/hax-toolbar-item.js","hax-toolbar-menu":"@haxtheweb/hax-body/lib/hax-toolbar-menu.js","hax-toolbar":"@haxtheweb/hax-body/lib/hax-toolbar.js","hax-tray-button":"@haxtheweb/hax-body/lib/hax-tray-button.js","hax-tray-upload":"@haxtheweb/hax-body/lib/hax-tray-upload.js","hax-tray":"@haxtheweb/hax-body/lib/hax-tray.js","hax-ui-styles":"@haxtheweb/hax-body/lib/hax-ui-styles.js","hax-upload-field":"@haxtheweb/hax-body/lib/hax-upload-field.js","hax-view-source":"@haxtheweb/hax-body/lib/hax-view-source.js","hax-cloud":"@haxtheweb/hax-cloud/hax-cloud.js","hax-logo":"@haxtheweb/hax-logo/hax-logo.js","haxcms-backend-beaker":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-beaker.js","haxcms-backend-demo":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-demo.js","haxcms-backend-nodejs":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-nodejs.js","haxcms-backend-php":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-php.js","haxcms-backend-userfs":"@haxtheweb/haxcms-elements/lib/core/backends/haxcms-backend-userfs.js","haxcms-darkmode-toggle":"@haxtheweb/haxcms-elements/lib/core/haxcms-darkmode-toggle.js","haxcms-editor-builder":"@haxtheweb/haxcms-elements/lib/core/haxcms-editor-builder.js","haxcms-outline-editor-dialog":"@haxtheweb/haxcms-elements/lib/core/haxcms-outline-editor-dialog.js","haxcms-site-builder":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-builder.js","haxcms-site-dashboard":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-dashboard.js","haxcms-site-editor-ui":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-editor-ui.js","haxcms-site-editor":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-editor.js","haxcms-site-insights":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-insights.js","haxcms-site-router":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-router.js","haxcms-site-store":"@haxtheweb/haxcms-elements/lib/core/haxcms-site-store.js","haxcms-toast":"@haxtheweb/haxcms-elements/lib/core/haxcms-toast.js","haxcms-button-add":"@haxtheweb/haxcms-elements/lib/core/micros/haxcms-button-add.js","haxcms-page-operations":"@haxtheweb/haxcms-elements/lib/core/micros/haxcms-page-operations.js","haxcms-basic-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-basic-theme.js","haxcms-blank-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-blank-theme.js","haxcms-json-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-json-theme.js","haxcms-minimalist-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-minimalist-theme.js","haxcms-print-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-print-theme.js","haxcms-slide-theme":"@haxtheweb/haxcms-elements/lib/core/themes/haxcms-slide-theme.js","haxcms-page-get-started":"@haxtheweb/haxcms-elements/lib/core/ui/haxcms-page-get-started.js","haxcms-dev-theme":"@haxtheweb/haxcms-elements/lib/development/haxcms-dev-theme.js","haxcms-theme-developer":"@haxtheweb/haxcms-elements/lib/development/haxcms-theme-developer.js","site-active-fields":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-fields.js","site-active-media-banner":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-media-banner.js","site-active-tags":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-tags.js","site-active-title":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-active-title.js","site-git-corner":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-git-corner.js","site-share-widget":"@haxtheweb/haxcms-elements/lib/ui-components/active-item/site-share-widget.js","site-children-block":"@haxtheweb/haxcms-elements/lib/ui-components/blocks/site-children-block.js","site-outline-block":"@haxtheweb/haxcms-elements/lib/ui-components/blocks/site-outline-block.js","site-recent-content-block":"@haxtheweb/haxcms-elements/lib/ui-components/blocks/site-recent-content-block.js","site-drawer":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-drawer.js","site-footer":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-footer.js","site-modal":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-modal.js","site-region":"@haxtheweb/haxcms-elements/lib/ui-components/layout/site-region.js","lesson-overview":"@haxtheweb/haxcms-elements/lib/ui-components/lesson-overview/lesson-overview.js","lesson-highlight":"@haxtheweb/haxcms-elements/lib/ui-components/lesson-overview/lib/lesson-highlight.js","active-when-visible":"@haxtheweb/haxcms-elements/lib/ui-components/magic/active-when-visible.js","site-ai-chat":"@haxtheweb/haxcms-elements/lib/ui-components/magic/site-ai-chat.js","site-collection-list":"@haxtheweb/haxcms-elements/lib/ui-components/magic/site-collection-list.js","site-view":"@haxtheweb/haxcms-elements/lib/ui-components/magic/site-view.js","site-breadcrumb":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-breadcrumb.js","site-dot-indicator":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-dot-indicator.js","site-menu-button":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-menu-button.js","site-menu-content":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-menu-content.js","site-menu":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-menu.js","site-top-menu":"@haxtheweb/haxcms-elements/lib/ui-components/navigation/site-top-menu.js","site-query-menu-slice":"@haxtheweb/haxcms-elements/lib/ui-components/query/site-query-menu-slice.js","site-query":"@haxtheweb/haxcms-elements/lib/ui-components/query/site-query.js","site-render-query":"@haxtheweb/haxcms-elements/lib/ui-components/query/site-render-query.js","site-home-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-home-route.js","site-print-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-print-route.js","site-random-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-random-route.js","site-tags-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-tags-route.js","site-theme-style-guide-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-theme-style-guide-route.js","site-views-route":"@haxtheweb/haxcms-elements/lib/ui-components/routes/site-views-route.js","site-print-button":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-print-button.js","site-random-content":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-random-content.js","site-remote-content":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-remote-content.js","site-rss-button":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-rss-button.js","site-search":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-search.js","site-title":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-title.js","site-uuid-link":"@haxtheweb/haxcms-elements/lib/ui-components/site/site-uuid-link.js","basic-template":"@haxtheweb/haxcms-elements/lib/ui-components/templates/basic-template.js","minimalist-template":"@haxtheweb/haxcms-elements/lib/ui-components/templates/minimalist-template.js","site-available-themes":"@haxtheweb/haxcms-elements/lib/ui-components/utilities/site-available-themes.js","haxma-theme":"@haxtheweb/haxma-theme/haxma-theme.js","haxor-slevin":"@haxtheweb/haxor-slevin/haxor-slevin.js","hex-picker":"@haxtheweb/hex-picker/hex-picker.js","hexagon-loader":"@haxtheweb/hexagon-loader/hexagon-loader.js","hex-a-gon":"@haxtheweb/hexagon-loader/lib/hex-a-gon.js","html-block":"@haxtheweb/html-block/html-block.js","i18n-manager":"@haxtheweb/i18n-manager/i18n-manager.js","iframe-loader":"@haxtheweb/iframe-loader/iframe-loader.js","loading-indicator":"@haxtheweb/iframe-loader/lib/loading-indicator.js","image-compare-slider":"@haxtheweb/image-compare-slider/image-compare-slider.js","image-inspector":"@haxtheweb/image-inspector/image-inspector.js","img-pan-zoom":"@haxtheweb/img-pan-zoom/img-pan-zoom.js","img-loader":"@haxtheweb/img-pan-zoom/lib/img-loader.js","img-view-modal":"@haxtheweb/img-view-modal/img-view-modal.js","img-view-viewer":"@haxtheweb/img-view-modal/lib/img-view-viewer.js","inline-audio":"@haxtheweb/inline-audio/inline-audio.js","journey-theme":"@haxtheweb/journey-theme/journey-theme.js","journey-menu":"@haxtheweb/journey-theme/lib/journey-menu.js","journey-sidebar-theme":"@haxtheweb/journey-theme/lib/journey-sidebar-theme.js","journey-topbar-theme":"@haxtheweb/journey-theme/lib/journey-topbar-theme.js","json-editor":"@haxtheweb/json-editor/json-editor.js","json-outline-schema":"@haxtheweb/json-outline-schema/json-outline-schema.js","jos-render":"@haxtheweb/json-outline-schema/lib/jos-render.js","jwt-login":"@haxtheweb/jwt-login/jwt-login.js","la-tex":"@haxtheweb/la-tex/la-tex.js","lazy-image":"@haxtheweb/lazy-image-helpers/lazy-image-helpers.js","lazy-import-discover":"@haxtheweb/lazy-import-discover/lazy-import-discover.js","learn-two-theme":"@haxtheweb/learn-two-theme/learn-two-theme.js","license-element":"@haxtheweb/license-element/license-element.js","lorem-data":"@haxtheweb/lorem-data/lorem-data.js","lrn-gitgraph":"@haxtheweb/lrn-gitgraph/lrn-gitgraph.js","lrn-math":"@haxtheweb/lrn-math/lrn-math.js","lrn-table":"@haxtheweb/lrn-table/lrn-table.js","lrn-vocab":"@haxtheweb/lrn-vocab/lrn-vocab.js","lrndesign-avatar":"@haxtheweb/lrndesign-avatar/lrndesign-avatar.js","lrndesign-bar":"@haxtheweb/lrndesign-chart/lib/lrndesign-bar.js","lrndesign-line":"@haxtheweb/lrndesign-chart/lib/lrndesign-line.js","lrndesign-pie":"@haxtheweb/lrndesign-chart/lib/lrndesign-pie.js","lrndesign-imagemap-hotspot":"@haxtheweb/lrndesign-imagemap/lib/lrndesign-imagemap-hotspot.js","lrndesign-imagemap":"@haxtheweb/lrndesign-imagemap/lrndesign-imagemap.js","lrndesign-sidenote":"@haxtheweb/lrndesign-sidenote/lrndesign-sidenote.js","lrndesign-timeline":"@haxtheweb/lrndesign-timeline/lrndesign-timeline.js","lrs-bridge-haxcms":"@haxtheweb/lrs-elements/lib/lrs-bridge-haxcms.js","lrs-bridge":"@haxtheweb/lrs-elements/lib/lrs-bridge.js","lrs-emitter":"@haxtheweb/lrs-elements/lib/lrs-emitter.js","lunr-search":"@haxtheweb/lunr-search/lunr-search.js","map-menu-builder":"@haxtheweb/map-menu/lib/map-menu-builder.js","map-menu-container":"@haxtheweb/map-menu/lib/map-menu-container.js","map-menu-header":"@haxtheweb/map-menu/lib/map-menu-header.js","map-menu-item":"@haxtheweb/map-menu/lib/map-menu-item.js","map-menu-submenu":"@haxtheweb/map-menu/lib/map-menu-submenu.js","map-menu":"@haxtheweb/map-menu/map-menu.js","mark-the-words":"@haxtheweb/mark-the-words/mark-the-words.js","matching-question":"@haxtheweb/matching-question/matching-question.js","md-block":"@haxtheweb/md-block/md-block.js","media-image":"@haxtheweb/media-image/media-image.js","media-quote":"@haxtheweb/media-quote/media-quote.js","meme-maker":"@haxtheweb/meme-maker/meme-maker.js","badge-sticker":"@haxtheweb/merit-badge/lib/badge-sticker.js","date-title":"@haxtheweb/merit-badge/lib/date-title.js","locked-badge":"@haxtheweb/merit-badge/lib/locked-badge.js","merit-badge":"@haxtheweb/merit-badge/merit-badge.js","micro-frontend-registry":"@haxtheweb/micro-frontend-registry/micro-frontend-registry.js","moar-sarcasm":"@haxtheweb/moar-sarcasm/moar-sarcasm.js","moment-element":"@haxtheweb/moment-element/moment-element.js","confetti-container":"@haxtheweb/multiple-choice/lib/confetti-container.js","short-answer-question":"@haxtheweb/multiple-choice/lib/short-answer-question.js","true-false-question":"@haxtheweb/multiple-choice/lib/true-false-question.js","multiple-choice":"@haxtheweb/multiple-choice/multiple-choice.js","midi-player":"@haxtheweb/music-player/lib/html-midi-player.js","music-player":"@haxtheweb/music-player/music-player.js","mutation-observer-import":"@haxtheweb/mutation-observer-import-mixin/mutation-observer-import-mixin.js","oer-schema":"@haxtheweb/oer-schema/oer-schema.js","outline-designer":"@haxtheweb/outline-designer/outline-designer.js","outline-player":"@haxtheweb/outline-player/outline-player.js","page-anchor":"@haxtheweb/page-break/lib/page-anchor.js","page-break-manager":"@haxtheweb/page-break/lib/page-break-manager.js","page-break-outline":"@haxtheweb/page-break/lib/page-break-outline.js","page-template":"@haxtheweb/page-break/lib/page-template.js","page-break":"@haxtheweb/page-break/page-break.js","page-contents-menu":"@haxtheweb/page-contents-menu/page-contents-menu.js","page-flag-comment":"@haxtheweb/page-flag/lib/page-flag-comment.js","page-flag":"@haxtheweb/page-flag/page-flag.js","page-scroll-position":"@haxtheweb/page-scroll-position/page-scroll-position.js","page-section":"@haxtheweb/page-section/page-section.js","paper-avatar":"@haxtheweb/paper-avatar/paper-avatar.js","paper-input-flagged":"@haxtheweb/paper-input-flagged/paper-input-flagged.js","paper-icon-step":"@haxtheweb/paper-stepper/lib/paper-icon-step.js","paper-icon-stepper":"@haxtheweb/paper-stepper/lib/paper-icon-stepper.js","paper-step":"@haxtheweb/paper-stepper/lib/paper-step.js","paper-stepper":"@haxtheweb/paper-stepper/paper-stepper.js","parallax-image":"@haxtheweb/parallax-image/parallax-image.js","pdf-browser-viewer":"@haxtheweb/pdf-browser-viewer/pdf-browser-viewer.js","person-testimonial":"@haxtheweb/person-testimonial/person-testimonial.js","place-holder":"@haxtheweb/place-holder/place-holder.js","play-list":"@haxtheweb/play-list/play-list.js","polaris-cta":"@haxtheweb/polaris-theme/lib/polaris-cta.js","polaris-flex-sidebar":"@haxtheweb/polaris-theme/lib/polaris-flex-sidebar.js","polaris-flex-theme":"@haxtheweb/polaris-theme/lib/polaris-flex-theme.js","polaris-invent-theme":"@haxtheweb/polaris-theme/lib/polaris-invent-theme.js","polaris-mark":"@haxtheweb/polaris-theme/lib/polaris-mark.js","polaris-story-card":"@haxtheweb/polaris-theme/lib/polaris-story-card.js","polaris-tile":"@haxtheweb/polaris-theme/lib/polaris-tile.js","polaris-theme":"@haxtheweb/polaris-theme/polaris-theme.js","portal-launcher":"@haxtheweb/portal-launcher/portal-launcher.js","post-card-photo":"@haxtheweb/post-card/lib/PostCardPhoto.js","post-card-postmark":"@haxtheweb/post-card/lib/PostCardPostmark.js","post-card-stamp":"@haxtheweb/post-card/lib/PostCardStamp.js","post-card":"@haxtheweb/post-card/post-card.js","pouch-db":"@haxtheweb/pouch-db/pouch-db.js","course-card":"@haxtheweb/product-card/lib/course-card.js","hax-element-card-list":"@haxtheweb/product-card/lib/hax-element-card-list.js","hax-element-list-selector":"@haxtheweb/product-card/lib/hax-element-list-selector.js","product-banner":"@haxtheweb/product-card/lib/product-banner.js","product-card":"@haxtheweb/product-card/product-card.js","product-glance":"@haxtheweb/product-glance/product-glance.js","product-offering":"@haxtheweb/product-offering/product-offering.js","progress-donut":"@haxtheweb/progress-donut/progress-donut.js","promise-progress-lite":"@haxtheweb/promise-progress/lib/promise-progress-lite.js","wc-preload-progress":"@haxtheweb/promise-progress/lib/wc-preload-progress.js","promise-progress":"@haxtheweb/promise-progress/promise-progress.js","qr-code":"@haxtheweb/q-r/lib/qr-code.js","q-r":"@haxtheweb/q-r/q-r.js","relative-heading-lite":"@haxtheweb/relative-heading/lib/relative-heading-lite.js","relative-heading-state-manager":"@haxtheweb/relative-heading/lib/relative-heading-state-manager.js","relative-heading":"@haxtheweb/relative-heading/relative-heading.js","performance-detect":"@haxtheweb/replace-tag/lib/PerformanceDetect.js","replace-tag":"@haxtheweb/replace-tag/replace-tag.js","responsive-grid-clear":"@haxtheweb/responsive-grid/lib/responsive-grid-clear.js","responsive-grid-col":"@haxtheweb/responsive-grid/lib/responsive-grid-col.js","responsive-grid-row":"@haxtheweb/responsive-grid/lib/responsive-grid-row.js","responsive-utility-element":"@haxtheweb/responsive-utility/lib/responsive-utility-element.js","responsive-utility":"@haxtheweb/responsive-utility/responsive-utility.js","retro-card":"@haxtheweb/retro-card/retro-card.js","rich-text-editor-button":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-button.js","rich-text-editor-emoji-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-emoji-picker.js","rich-text-editor-heading-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-heading-picker.js","rich-text-editor-icon-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-icon-picker.js","rich-text-editor-image":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-image.js","rich-text-editor-link":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-link.js","rich-text-editor-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-picker.js","rich-text-editor-prompt-button":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-prompt-button.js","rich-text-editor-source-code":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-source-code.js","rich-text-editor-symbol-picker":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-symbol-picker.js","rich-text-editor-underline":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-underline.js","rich-text-editor-unlink":"@haxtheweb/rich-text-editor/lib/buttons/rich-text-editor-unlink.js","link-preview-card":"@haxtheweb/rich-text-editor/lib/open-apis/link-preview-card.js","rich-text-editor-clipboard":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-clipboard.js","rich-text-editor-highlight":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-highlight.js","rich-text-editor-prompt":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-prompt.js","rich-text-editor-source":"@haxtheweb/rich-text-editor/lib/singletons/rich-text-editor-source.js","rich-text-editor-breadcrumbs":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-breadcrumbs.js","rich-text-editor-toolbar-full":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-toolbar-full.js","rich-text-editor-toolbar-mini":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-toolbar-mini.js","rich-text-editor-toolbar":"@haxtheweb/rich-text-editor/lib/toolbars/rich-text-editor-toolbar.js","rich-text-editor":"@haxtheweb/rich-text-editor/rich-text-editor.js","rpg-character":"@haxtheweb/rpg-character/rpg-character.js","runkit-embed":"@haxtheweb/runkit-embed/runkit-embed.js","screen-recorder":"@haxtheweb/screen-recorder/screen-recorder.js","scroll-button":"@haxtheweb/scroll-button/scroll-button.js","self-check":"@haxtheweb/self-check/self-check.js","shadow-style":"@haxtheweb/shadow-style/shadow-style.js","simple-autocomplete-text-trigger":"@haxtheweb/simple-autocomplete/lib/simple-autocomplete-text-trigger.js","simple-autocomplete":"@haxtheweb/simple-autocomplete/simple-autocomplete.js","simple-blog-card":"@haxtheweb/simple-blog-card/simple-blog-card.js","simple-blog-footer":"@haxtheweb/simple-blog/lib/simple-blog-footer.js","simple-blog-header":"@haxtheweb/simple-blog/lib/simple-blog-header.js","simple-blog-listing":"@haxtheweb/simple-blog/lib/simple-blog-listing.js","simple-blog-overview":"@haxtheweb/simple-blog/lib/simple-blog-overview.js","simple-blog-post":"@haxtheweb/simple-blog/lib/simple-blog-post.js","simple-blog":"@haxtheweb/simple-blog/simple-blog.js","simple-colors-shared-styles":"@haxtheweb/simple-colors-shared-styles/simple-colors-shared-styles.js","simple-colors-swatch-info":"@haxtheweb/simple-colors/lib/demo/simple-colors-swatch-info.js","simple-colors-swatches":"@haxtheweb/simple-colors/lib/demo/simple-colors-swatches.js","simple-colors-picker":"@haxtheweb/simple-colors/lib/simple-colors-picker.js","simple-colors":"@haxtheweb/simple-colors/simple-colors.js","lrnsys-button":"@haxtheweb/simple-cta/lib/lrnsys-button.js","simple-cta":"@haxtheweb/simple-cta/simple-cta.js","simple-datetime":"@haxtheweb/simple-datetime/simple-datetime.js","simple-emoji":"@haxtheweb/simple-emoji/simple-emoji.js","simple-context-menu":"@haxtheweb/simple-fields/lib/simple-context-menu.js","simple-fields-array":"@haxtheweb/simple-fields/lib/simple-fields-array.js","simple-fields-code":"@haxtheweb/simple-fields/lib/simple-fields-code.js","simple-fields-combo":"@haxtheweb/simple-fields/lib/simple-fields-combo.js","simple-fields-container":"@haxtheweb/simple-fields/lib/simple-fields-container.js","simple-fields-field":"@haxtheweb/simple-fields/lib/simple-fields-field.js","simple-fields-fieldset":"@haxtheweb/simple-fields/lib/simple-fields-fieldset.js","simple-fields-form-lite":"@haxtheweb/simple-fields/lib/simple-fields-form-lite.js","simple-fields-form":"@haxtheweb/simple-fields/lib/simple-fields-form.js","simple-fields-html-block":"@haxtheweb/simple-fields/lib/simple-fields-html-block.js","simple-fields-lite":"@haxtheweb/simple-fields/lib/simple-fields-lite.js","simple-fields-tab":"@haxtheweb/simple-fields/lib/simple-fields-tab.js","simple-fields-tabs":"@haxtheweb/simple-fields/lib/simple-fields-tabs.js","simple-fields-tag-list":"@haxtheweb/simple-fields/lib/simple-fields-tag-list.js","simple-fields-upload":"@haxtheweb/simple-fields/lib/simple-fields-upload.js","simple-fields-url-combo-item":"@haxtheweb/simple-fields/lib/simple-fields-url-combo-item.js","simple-fields-url-combo":"@haxtheweb/simple-fields/lib/simple-fields-url-combo.js","simple-tag-lite":"@haxtheweb/simple-fields/lib/simple-tag-lite.js","simple-tag":"@haxtheweb/simple-fields/lib/simple-tag.js","simple-tags":"@haxtheweb/simple-fields/lib/simple-tags.js","simple-fields":"@haxtheweb/simple-fields/simple-fields.js","simple-icon-picker":"@haxtheweb/simple-icon-picker/simple-icon-picker.js","simple-icon-button-lite":"@haxtheweb/simple-icon/lib/simple-icon-button-lite.js","simple-icon-button":"@haxtheweb/simple-icon/lib/simple-icon-button.js","simple-icon-lite":"@haxtheweb/simple-icon/lib/simple-icon-lite.js","simple-iconset-demo":"@haxtheweb/simple-icon/lib/simple-iconset-demo.js","simple-iconset":"@haxtheweb/simple-icon/lib/simple-iconset.js","simple-icon":"@haxtheweb/simple-icon/simple-icon.js","simple-img":"@haxtheweb/simple-img/simple-img.js","simple-camera-snap":"@haxtheweb/simple-login/lib/simple-camera-snap.js","simple-login-avatar":"@haxtheweb/simple-login/lib/simple-login-avatar.js","simple-login-camera":"@haxtheweb/simple-login/lib/simple-login-camera.js","simple-login":"@haxtheweb/simple-login/simple-login.js","simple-modal-template":"@haxtheweb/simple-modal/lib/simple-modal-template.js","simple-modal":"@haxtheweb/simple-modal/simple-modal.js","simple-emoji-picker":"@haxtheweb/simple-picker/lib/simple-emoji-picker.js","simple-picker-option":"@haxtheweb/simple-picker/lib/simple-picker-option.js","simple-symbol-picker":"@haxtheweb/simple-picker/lib/simple-symbol-picker.js","simple-picker":"@haxtheweb/simple-picker/simple-picker.js","simple-popover-manager":"@haxtheweb/simple-popover/lib/simple-popover-manager.js","simple-popover-selection":"@haxtheweb/simple-popover/lib/simple-popover-selection.js","simple-tour":"@haxtheweb/simple-popover/lib/simple-tour.js","simple-popover":"@haxtheweb/simple-popover/simple-popover.js","simple-progress":"@haxtheweb/simple-progress/simple-progress.js","simple-range-input":"@haxtheweb/simple-range-input/simple-range-input.js","simple-search-content":"@haxtheweb/simple-search/lib/simple-search-content.js","simple-search-match":"@haxtheweb/simple-search/lib/simple-search-match.js","simple-search":"@haxtheweb/simple-search/simple-search.js","simple-toast-el":"@haxtheweb/simple-toast/lib/simple-toast-el.js","simple-toast":"@haxtheweb/simple-toast/simple-toast.js","simple-button-grid":"@haxtheweb/simple-toolbar/lib/simple-button-grid.js","simple-toolbar-button-group":"@haxtheweb/simple-toolbar/lib/simple-toolbar-button-group.js","simple-toolbar-button":"@haxtheweb/simple-toolbar/lib/simple-toolbar-button.js","simple-toolbar-field":"@haxtheweb/simple-toolbar/lib/simple-toolbar-field.js","simple-toolbar-menu-item":"@haxtheweb/simple-toolbar/lib/simple-toolbar-menu-item.js","simple-toolbar-menu":"@haxtheweb/simple-toolbar/lib/simple-toolbar-menu.js","simple-toolbar-more-button":"@haxtheweb/simple-toolbar/lib/simple-toolbar-more-button.js","simple-toolbar":"@haxtheweb/simple-toolbar/simple-toolbar.js","simple-tooltip":"@haxtheweb/simple-tooltip/simple-tooltip.js","social-share-link":"@haxtheweb/social-share-link/social-share-link.js","sorting-option":"@haxtheweb/sorting-question/lib/sorting-option.js","sorting-question":"@haxtheweb/sorting-question/sorting-question.js","spacebook-theme":"@haxtheweb/spacebook-theme/spacebook-theme.js","spotify-embed":"@haxtheweb/spotify-embed/spotify-embed.js","star-rating":"@haxtheweb/star-rating/star-rating.js","stop-note":"@haxtheweb/stop-note/stop-note.js","super-daemon-row":"@haxtheweb/super-daemon/lib/super-daemon-row.js","super-daemon-search":"@haxtheweb/super-daemon/lib/super-daemon-search.js","super-daemon-toast":"@haxtheweb/super-daemon/lib/super-daemon-toast.js","super-daemon-ui":"@haxtheweb/super-daemon/lib/super-daemon-ui.js","super-daemon":"@haxtheweb/super-daemon/super-daemon.js","tagging-question":"@haxtheweb/tagging-question/tagging-question.js","terrible-best-themes":"@haxtheweb/terrible-themes/lib/terrible-best-themes.js","terrible-outlet-themes":"@haxtheweb/terrible-themes/lib/terrible-outlet-themes.js","terrible-productionz-themes":"@haxtheweb/terrible-themes/lib/terrible-productionz-themes.js","terrible-resume-themes":"@haxtheweb/terrible-themes/lib/terrible-resume-themes.js","terrible-themes":"@haxtheweb/terrible-themes/terrible-themes.js","training-button":"@haxtheweb/training-theme/lib/training-button.js","training-top":"@haxtheweb/training-theme/lib/training-top.js","training-theme":"@haxtheweb/training-theme/training-theme.js","twitter-embed-vanilla":"@haxtheweb/twitter-embed/lib/twitter-embed-vanilla.js","twitter-embed":"@haxtheweb/twitter-embed/twitter-embed.js","type-writer":"@haxtheweb/type-writer/type-writer.js","un-sdg":"@haxtheweb/un-sdg/un-sdg.js","undo-manager":"@haxtheweb/undo-manager/undo-manager.js","unity-webgl":"@haxtheweb/unity-webgl/unity-webgl.js","user-action":"@haxtheweb/user-action/user-action.js","user-scaffold":"@haxtheweb/user-scaffold/user-scaffold.js","lecture-anchor":"@haxtheweb/video-player/lib/lecture-anchor.js","lecture-player":"@haxtheweb/video-player/lib/lecture-player.js","video-player":"@haxtheweb/video-player/video-player.js","vocab-term":"@haxtheweb/vocab-term/vocab-term.js","voice-recorder":"@haxtheweb/voice-recorder/voice-recorder.js","wc-registry":"@haxtheweb/wc-autoload/wc-autoload.js","web-container-doc-player":"@haxtheweb/web-container/lib/web-container-doc-player.js","web-container-wc-registry-docs":"@haxtheweb/web-container/lib/web-container-wc-registry-docs.js","web-container":"@haxtheweb/web-container/web-container.js","wikipedia-query":"@haxtheweb/wikipedia-query/wikipedia-query.js","word-count":"@haxtheweb/word-count/word-count.js","wysiwyg-hax":"@haxtheweb/wysiwyg-hax/wysiwyg-hax.js","lit-virtualizer":"@lit-labs/virtualizer/lit-virtualizer.js","app-box":"@polymer/app-layout/app-box/app-box.js","app-drawer-layout":"@polymer/app-layout/app-drawer-layout/app-drawer-layout.js","app-drawer":"@polymer/app-layout/app-drawer/app-drawer.js","app-header-layout":"@polymer/app-layout/app-header-layout/app-header-layout.js","app-header":"@polymer/app-layout/app-header/app-header.js","x-container":"@polymer/app-layout/app-scroll-effects/test/x-container.js","app-toolbar":"@polymer/app-layout/app-toolbar/app-toolbar.js","iron-a11y-announcer":"@polymer/iron-a11y-announcer/iron-a11y-announcer.js","iron-a11y-keys":"@polymer/iron-a11y-keys/iron-a11y-keys.js","iron-ajax":"@polymer/iron-ajax/iron-ajax.js","iron-request":"@polymer/iron-ajax/iron-request.js","iron-autogrow-textarea":"@polymer/iron-autogrow-textarea/iron-autogrow-textarea.js","iron-icon":"@polymer/iron-icon/iron-icon.js","iron-iconset-svg":"@polymer/iron-iconset-svg/iron-iconset-svg.js","iron-image":"@polymer/iron-image/iron-image.js","iron-input":"@polymer/iron-input/iron-input.js","iron-list":"@polymer/iron-list/iron-list.js","iron-media-query":"@polymer/iron-media-query/iron-media-query.js","iron-meta":"@polymer/iron-meta/iron-meta.js","x-app":"@polymer/iron-resizable-behavior/demo/src/x-app.js","x-puck":"@polymer/iron-resizable-behavior/demo/src/x-puck.js","marked-element":"@polymer/marked-element/marked-element.js","paper-card":"@polymer/paper-card/paper-card.js","paper-input-char-counter":"@polymer/paper-input/paper-input-char-counter.js","paper-input-container":"@polymer/paper-input/paper-input-container.js","paper-input-error":"@polymer/paper-input/paper-input-error.js","paper-input":"@polymer/paper-input/paper-input.js","paper-textarea":"@polymer/paper-input/paper-textarea.js","paper-spinner-lite":"@polymer/paper-spinner/paper-spinner-lite.js","paper-spinner":"@polymer/paper-spinner/paper-spinner.js","array-selector":"@polymer/polymer/lib/elements/array-selector.js","custom-style":"@polymer/polymer/lib/elements/custom-style.js","dom-bind":"@polymer/polymer/lib/elements/dom-bind.js","dom-if":"@polymer/polymer/lib/elements/dom-if.js","dom-module":"@polymer/polymer/lib/elements/dom-module.js","dom-repeat":"@polymer/polymer/lib/elements/dom-repeat.js","vaadin-button":"@vaadin/vaadin-button/src/vaadin-button.js","vaadin-checkbox-group":"@vaadin/vaadin-checkbox/src/vaadin-checkbox-group.js","vaadin-checkbox":"@vaadin/vaadin-checkbox/src/vaadin-checkbox.js","vaadin-grid-column-group":"@vaadin/vaadin-grid/src/vaadin-grid-column-group.js","vaadin-grid-column":"@vaadin/vaadin-grid/src/vaadin-grid-column.js","vaadin-grid-filter-column":"@vaadin/vaadin-grid/src/vaadin-grid-filter-column.js","vaadin-grid-filter":"@vaadin/vaadin-grid/src/vaadin-grid-filter.js","vaadin-grid-scroller":"@vaadin/vaadin-grid/src/vaadin-grid-scroller.js","vaadin-grid-selection-column":"@vaadin/vaadin-grid/src/vaadin-grid-selection-column.js","vaadin-grid-sort-column":"@vaadin/vaadin-grid/src/vaadin-grid-sort-column.js","vaadin-grid-sorter":"@vaadin/vaadin-grid/src/vaadin-grid-sorter.js","vaadin-grid-templatizer":"@vaadin/vaadin-grid/src/vaadin-grid-templatizer.js","vaadin-grid-tree-column":"@vaadin/vaadin-grid/src/vaadin-grid-tree-column.js","vaadin-grid-tree-toggle":"@vaadin/vaadin-grid/src/vaadin-grid-tree-toggle.js","vaadin-grid":"@vaadin/vaadin-grid/src/vaadin-grid.js","vaadin-lumo-styles":"@vaadin/vaadin-lumo-styles/version.js","vaadin-material-styles":"@vaadin/vaadin-material-styles/version.js","vaadin-progress-bar":"@vaadin/vaadin-progress-bar/src/vaadin-progress-bar.js","vaadin-email-field":"@vaadin/vaadin-text-field/src/vaadin-email-field.js","vaadin-integer-field":"@vaadin/vaadin-text-field/src/vaadin-integer-field.js","vaadin-number-field":"@vaadin/vaadin-text-field/src/vaadin-number-field.js","vaadin-password-field":"@vaadin/vaadin-text-field/src/vaadin-password-field.js","vaadin-text-area":"@vaadin/vaadin-text-field/src/vaadin-text-area.js","vaadin-text-field":"@vaadin/vaadin-text-field/src/vaadin-text-field.js","vaadin-upload-file":"@vaadin/vaadin-upload/src/vaadin-upload-file.js","vaadin-upload":"@vaadin/vaadin-upload/src/vaadin-upload.js","scrollable-component":"scrollable-component/index.js","web-dialog":"web-dialog/web-dialog.js"} \ No newline at end of file