diff --git a/.claude/skills/github-issue-fixy/SKILL.md b/.claude/skills/github-issue-fixy/SKILL.md new file mode 100644 index 00000000..b0e53f58 --- /dev/null +++ b/.claude/skills/github-issue-fixy/SKILL.md @@ -0,0 +1,309 @@ +--- +name: github-issue-solver +description: Accept a GitHub issue URL or issue details, create a comprehensive plan (in plan mode - shows plan first), then implement the solution including tests and documentation, create/update release notes for Home Assistant users, and update the README roadmap for new features. Use when the user provides a GitHub issue or asks to solve an issue. ALWAYS presents the plan first and waits for user approval before implementing. +--- + +# GitHub Issue Solver + +This skill guides you through solving GitHub issues for the Home Assistant device-card project. It ensures comprehensive planning, testing, documentation, and release notes. + +## When to Use + +- Use this skill when the user provides a GitHub issue URL or issue details +- Use when asked to solve, fix, or implement something from a GitHub issue +- Use when planning work based on an issue or feature request + +## Instructions + +**IMPORTANT: This skill runs in PLAN MODE first. You MUST present a comprehensive plan to the user and wait for their approval before implementing any code changes.** + +### Step 1: Analyze the GitHub Issue + +1. **Fetch the issue details**: + - If a GitHub issue URL is provided, fetch it using the web fetch tool or ask the user for the issue details + - Extract key information: + - Issue title and description + - Issue type (bug fix, feature request, enhancement, etc.) + - Labels and milestones + - Comments and discussion + - Related issues or PRs + +2. **Understand the requirements**: + - Identify what needs to be fixed or implemented + - Note any edge cases or special considerations mentioned + - Check if there are any related issues or dependencies + +### Step 2: Create and Present the Plan (STOP HERE - DO NOT IMPLEMENT YET) + +1. **Break down the work**: + - List all files that need to be created or modified + - Identify the core functionality changes required + - Note any dependencies or related components + - Review existing code patterns in the project for consistency + +2. **Create a comprehensive task list**: + - Use the todo_write tool to create a structured task list with all tasks marked as "pending" + - Include detailed tasks for: + - Code implementation (specific files and functions) + - Test creation/updates (specific test files and test cases) + - Documentation updates (specific sections and files) + - Release notes (draft the entry) + - README roadmap update (if new feature, draft the entry) + +3. **Present the plan to the user**: + - **CRITICAL: You MUST stop here and present the complete plan** + - Show a clear, formatted plan including: + - Summary of the issue and what needs to be done + - List of files to be created/modified + - Overview of code changes needed + - Test strategy and test cases + - Documentation updates planned + - Draft release notes entry + - Draft roadmap entry (if new feature) + - **Wait for user approval before proceeding to Step 3** + - Use language like: "Here's my plan. Please review and let me know if you'd like me to proceed with implementation." + +### Step 3: Implement the Solution (ONLY AFTER USER APPROVAL) + +**DO NOT proceed to this step until the user has reviewed and approved the plan from Step 2.** + +1. **Code changes**: + - Follow TypeScript best practices + - Match the existing code style (check `.prettierrc` for formatting) + - Ensure type safety (check `tsconfig.json`) + - Follow the project's file structure conventions + +2. **Key areas to consider**: + - `src/cards/` - Card implementations + - `src/delegates/` - Business logic and data retrieval + - `src/html/` - HTML rendering components + - `src/hass/` - Home Assistant integration code + - `src/common/` - Shared utilities + - `src/types/` - TypeScript type definitions + +### Step 4: Write Tests + +1. **Test coverage**: + - **PREFER updating existing tests** over creating many new test cases + - When possible, add assertions to existing tests that already cover similar functionality + - Create or update test files in `test/` directory matching the source structure + - Follow existing test patterns (check `test/` directory for examples) + - Use Mocha test framework (check `.mocharc.json` and `mocha.setup.ts`) + +2. **Test requirements**: + - **Keep tests concise**: Aim for 1-2 focused test cases, or update existing tests to cover multiple scenarios + - Unit tests for new functions and methods + - Edge case testing (when necessary, but consolidate into existing tests when possible) + - Integration tests if applicable + - Ensure tests pass: `yarn test` or `npm test` + +3. **Test file naming**: + - Test files should match source files with `.spec.ts` extension + - Example: `src/cards/device-card/card.ts` → `test/cards/device-card/card.spec.ts` + +4. **Test consolidation strategy**: + - **Reuse existing setup/mocks**: Before creating new test cases, check if existing tests already have similar mocks, stubs, or setup code that can be reused + - Review existing tests to see if new functionality can be tested alongside existing assertions + - Update existing test data/mocks to include new scenarios rather than creating separate tests + - Only create new test cases when the functionality is truly distinct and cannot be tested within existing tests + - **Avoid duplicating setup code**: If multiple tests need similar mocks or stubs, consider: + - Adding shared setup to `beforeEach` hooks + - Extending existing mock data structures + - Creating helper functions for common test setup + +5. **Test quality review**: + - **Review the entire test file** after making changes to ensure: + - No duplicative tests that test the same thing in slightly different ways + - No low-value tests that don't add meaningful coverage + - Tests are well-organized and follow the existing patterns + - Setup code is not unnecessarily duplicated across tests + - If you notice duplicative or low-value tests while working, consider removing or consolidating them + - Focus on tests that provide real value: catching bugs, ensuring correctness, and documenting expected behavior + +### Step 5: Update Documentation + +1. **README.md updates**: + - If adding a new feature, add it to the "Features" section + - Update "Configuration Options" table if adding new config options + - Add example configurations in "Example Configurations" section + - Update the "Project Roadmap" section (see Step 7) + +2. **Code comments**: + - Add JSDoc comments for public functions and classes + - Document complex logic and algorithms + - Explain non-obvious decisions + +3. **Translation files** (if adding user-facing strings): + - Update `src/translations/en.json` (English) + - Consider updating other language files: `fr.json`, `pt.json`, `ru.json` + - Follow the existing translation structure + +### Step 6: Create/Update Release Notes + +1. **Release notes format**: + - Create or update a release notes file (check if one exists, otherwise create `RELEASE_NOTES.md` or similar) + - Format entries clearly with issue/PR references + +2. **Release notes structure**: + - **Main header**: Release title with 2 random emojis at the end + - **Each bug/feature**: Use `##` heading (level 2) for each bug fix or feature, starting with a relevant emoji + - **Notes under each section**: Small descriptive notes under each heading + +3. **Release notes content**: + - **Bug fixes**: "Fixed [description] - fixes #[issue-number]" + - **New features**: "Added [feature name] - thanks @[username] - fixes #[issue-number]" + - **Enhancements**: "Improved [description] - fixes #[issue-number]" + - **Breaking changes**: Clearly mark with "⚠️ BREAKING CHANGE:" prefix + +4. **Example format**: + + ```markdown + # Hidden Entity Filtering & Dark Mode!🎉✨ + + ## 🔍 Hidden Entity Filtering + + Hidden entities are now automatically filtered out from device cards, matching Home Assistant's more-info popup behavior - thanks @warmfire540 - fixes #43 + + ## 🌙 Dark Mode Support + + Added dark mode theme option for better visibility in low-light environments - thanks @username - fixes #123 + ``` + +5. **Home Assistant user-friendly language**: + - Write in clear, non-technical language + - Focus on what users can do or what problems are solved + - Avoid internal implementation details + - Use Home Assistant terminology where appropriate + +### Step 7: Update README Roadmap (for New Features) + +1. **Check if it's a new feature**: + - If the issue is a feature request or adds new functionality + - If it's a bug fix, skip this step + +2. **Add to roadmap**: + - Locate the "Project Roadmap" section in README.md + - Add a new entry in the format: + ```markdown + - [ ] **`Feature Name`**: Description of feature - thanks @[username] + ``` + - Use present tense for completed features (change `[ ]` to `[x]` after implementation) + - Use future tense for planned features + +3. **Thank the contributor**: + - If the issue was opened by a GitHub user, include their username + - Format: `- thanks @[username]` + - If multiple contributors, list them all + +### Step 8: Final Checklist + +Before completing, verify: + +- [ ] All code changes are implemented +- [ ] All tests are written and passing +- [ ] **Test quality**: Reviewed test files for duplicative or low-value tests that could be removed +- [ ] **Test reuse**: Confirmed that existing test setup/mocks were reused where possible +- [ ] Documentation is updated (README.md, code comments) +- [ ] Release notes are created/updated +- [ ] README roadmap is updated (if new feature) +- [ ] Code follows project style guidelines +- [ ] TypeScript types are correct +- [ ] No linter errors +- [ ] Translation files updated (if needed) + +### Step 9: Summary + +Provide a summary of: + +- What was implemented +- Files created/modified +- Test coverage added +- Documentation updates +- Release notes entry +- Roadmap update (if applicable) + +## Notes + +- **CRITICAL**: This skill operates in PLAN MODE - always present the plan first and wait for user approval +- Never implement code changes without showing the plan and getting user confirmation +- Always maintain consistency with existing code patterns +- Follow the project's TypeScript and testing conventions +- Ensure backward compatibility unless it's a breaking change +- Consider Home Assistant version compatibility +- Test with multiple browsers if UI changes are involved +- Keep release notes user-friendly and focused on user benefits + +## Plan Presentation Format + +When presenting the plan, use this structure: + +````markdown +## Plan for Issue #[number]: [Title] + +### Issue Summary + +[Brief summary of what needs to be done] + +### Files to Create/Modify + +- `path/to/file1.ts` - [what will change] +- `path/to/file2.ts` - [what will change] +- ... + +### Implementation Approach + +[High-level description of how you'll implement the solution] + +### Test Strategy + +- Test file: `test/path/to/file1.spec.ts` + - **Prefer updating existing tests** rather than creating many new test cases + - **Reuse existing setup/mocks**: [describe what existing mocks or setup will be reused] + - If updating existing test: [which test and what will be added] + - If new test needed: [brief description of why and what it covers, and how it reuses existing setup] + - Aim for 1-2 focused test cases total + - **Review test file**: Check for any duplicative or low-value tests that could be removed or consolidated + +### Documentation Updates + +- README.md: [what sections will be updated] +- Code comments: [what will be documented] +- Translations: [if applicable] + +### Release Notes Draft + +```markdown +## [Emoji] [Feature/Bug Name] + +Brief description, yaml example, doc links, etc. +``` +```` + +### Roadmap Entry Draft (if new feature) + +[Draft of the roadmap entry] + +--- + +**Ready to proceed?** Please review the plan above and let me know if you'd like me to continue with implementation. + +``` + +## Example Workflow + +1. User provides: "Solve issue #123: Add dark mode support" +2. Fetch issue details from GitHub +3. **PLAN MODE**: Create comprehensive plan: + - Files to modify: `src/cards/device-card/styles.ts`, `src/cards/device-card/types.ts`, `src/cards/device-card/editor.ts` + - Tests: Update existing test in `test/cards/device-card/card.spec.ts` to include dark mode scenarios (prefer updating over creating new tests) + - Documentation: Update README.md Features section, add config option to table + - Release notes draft: "## Dark Mode Support\n\nAdded dark mode theme option" + - Roadmap draft: "- [ ] **`Dark mode support`**: Add dark mode theme option - thanks @username" +4. **PRESENT PLAN TO USER** - Wait for approval +5. **ONLY AFTER APPROVAL**: Implement code changes +6. **ONLY AFTER APPROVAL**: Create/update test files +7. **ONLY AFTER APPROVAL**: Update documentation +8. **ONLY AFTER APPROVAL**: Create/update release notes +9. **ONLY AFTER APPROVAL**: Update roadmap +10. Summary: List all changes made +``` diff --git a/README.md b/README.md index 30666583..3ed5e262 100644 --- a/README.md +++ b/README.md @@ -127,4 +127,4 @@ This project is protected under the MIT License. For more details, refer to the - [x] **`Frosted Glass Theme Support`**: automatic detection and styling for Frosted Glass themes with transparent blurred card effects - thanks @devkaiwang - [x] **`Sensor Improvements`**: feature requests to make sensors awesome - thanks @MelleD - [x] **`Brightness Slider`**: entity transforms into slider - thanks @tmaihoff, @hfalk -- [x] **`Entity Badges`**: dynamic badge overlays- thanks @ojm88 \ No newline at end of file +- [x] **`Entity Badges`**: dynamic badge overlays- thanks @ojm88 diff --git a/docs/configuration/BADGE-CONFIGURATION.md b/docs/configuration/BADGE-CONFIGURATION.md index 02000207..8e8fa38b 100644 --- a/docs/configuration/BADGE-CONFIGURATION.md +++ b/docs/configuration/BADGE-CONFIGURATION.md @@ -159,7 +159,7 @@ When using state-based badges (no `mode` specified), the `states` array uses the | Name | Type | Default | Description | | ---------- | ------ | ------------ | -------------------------------------------------------- | | state | string | **Required** | Entity state or attribute value to match exactly | -| operator | string | `eq` | Comparison operator: `eq` (equal) or `ne` (not equal) | +| operator | string | `eq` | Comparison operator: `eq` (equal) or `ne` (not equal) | | icon_color | string | **Required** | Color to use when this state is active | | icon | string | none | Icon to use when this state is active | | attribute | string | none | Optional attribute name to match instead of entity state | @@ -292,7 +292,7 @@ entities: - state: 'ok' operator: ne icon: mdi:alert-circle - icon_color: red # Badge appears for all states except 'ok' + icon_color: red # Badge appears for all states except 'ok' ``` ## Badge Styling diff --git a/docs/configuration/ENTITY-COLOR-CONFIGURATION.md b/docs/configuration/ENTITY-COLOR-CONFIGURATION.md index ca68dede..579d8d1b 100644 --- a/docs/configuration/ENTITY-COLOR-CONFIGURATION.md +++ b/docs/configuration/ENTITY-COLOR-CONFIGURATION.md @@ -107,7 +107,7 @@ entities: icon_color: green - state: 'ok' operator: ne - icon_color: red # All other states show red + icon_color: red # All other states show red ``` This is particularly useful for entities with enum states where you want to match "all states except X" without listing every possible state explicitly. diff --git a/docs/configuration/ENTITY-CONFIGURATION.md b/docs/configuration/ENTITY-CONFIGURATION.md index 17498155..79316c15 100644 --- a/docs/configuration/ENTITY-CONFIGURATION.md +++ b/docs/configuration/ENTITY-CONFIGURATION.md @@ -309,7 +309,7 @@ entities: icon_color: green - state: 'ok' operator: ne - icon_color: red # All other states show red + icon_color: red # All other states show red ``` ## State Matching Operators @@ -330,7 +330,7 @@ entities: icon_color: green - state: 'ok' operator: ne - icon_color: red # All states except 'ok' show red + icon_color: red # All states except 'ok' show red ``` **Note**: State configurations are evaluated in order, so more specific matches (like `eq`) should be placed before broader matches (like `ne`). diff --git a/mocha.setup.ts b/mocha.setup.ts index 3cf42663..ca057bfe 100644 --- a/mocha.setup.ts +++ b/mocha.setup.ts @@ -17,8 +17,12 @@ global.window = dom.window as any; global.document = dom.window.document; global.requestAnimationFrame = (callback) => setTimeout(callback, 0); // Ensure globalThis also points to window for addEventListener support -(globalThis as any).addEventListener = dom.window.addEventListener.bind(dom.window); -(globalThis as any).removeEventListener = dom.window.removeEventListener.bind(dom.window); +(globalThis as any).addEventListener = dom.window.addEventListener.bind( + dom.window, +); +(globalThis as any).removeEventListener = dom.window.removeEventListener.bind( + dom.window, +); (globalThis as any).dispatchEvent = dom.window.dispatchEvent.bind(dom.window); // Add missing DOM features diff --git a/package.json b/package.json index b2f87785..9ba5f37d 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "devDependencies": { "@istanbuljs/nyc-config-typescript": "^1.0.2", "@open-wc/testing": "^4.0.0", - "@parcel/transformer-inline-string": "^2.16.3", + "@parcel/transformer-inline-string": "^2.16.4", "@testing-library/dom": "^10.4.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/chai": "^5.2.3", @@ -31,11 +31,11 @@ "@types/mocha": "^10.0.10", "@types/sinon": "^21.0.0", "chai": "^6.2.2", - "jsdom": "^27.4.0", + "jsdom": "^28.1.0", "mocha": "^11.7.5", "nyc": "^17.1.0", - "parcel": "^2.16.3", - "prettier": "3.8.0", + "parcel": "^2.16.4", + "prettier": "3.8.1", "prettier-plugin-organize-imports": "^4.3.0", "proxyquire": "^2.1.3", "sinon": "^21.0.1", diff --git a/src/cards/components/badge/badge.ts b/src/cards/components/badge/badge.ts index bcebfd17..6d9af45d 100644 --- a/src/cards/components/badge/badge.ts +++ b/src/cards/components/badge/badge.ts @@ -1,15 +1,16 @@ import { HassUpdateMixin } from '@cards/mixins/hass-update-mixin'; -import { getState } from '@delegates/retrievers/state'; +import { SubscribeEntityStateMixin } from '@cards/mixins/subscribe-entity-state-mixin'; import { getMatchingBadgeState } from '@delegates/utils/badge-state'; import { renderTileBadge } from '@hass/panels/lovelace/cards/tile/badges/tile-badge'; -import type { HomeAssistant } from '@hass/types'; import { stylesToHostCss } from '@theme/util/style-converter'; +import type { Config } from '@type/config'; import type { BadgeConfig } from '@type/config/entity'; -import type { EntityInformation } from '@type/room'; +import { d } from '@util/debug'; import { CSSResult, LitElement, html, nothing, type TemplateResult } from 'lit'; -import { property, state } from 'lit/decorators.js'; +import { property } from 'lit/decorators.js'; import { styleMap } from 'lit/directives/style-map.js'; import { styles } from './styles'; +const equal = require('fast-deep-equal'); /** * Badge Component @@ -17,23 +18,18 @@ import { styles } from './styles'; * A small Lit element that renders a badge overlay for an entity. * Badges can display entity state icons or custom icons based on configuration. */ -export class Badge extends HassUpdateMixin(LitElement) { - /** - * Home Assistant instance - */ - private _hass!: HomeAssistant; - +export class Badge extends SubscribeEntityStateMixin( + HassUpdateMixin(LitElement), +) { /** * Badge configuration */ - @property({ type: Object }) - config!: BadgeConfig; + private _config!: BadgeConfig; /** - * Parent entity information + * Card config for debug */ - @property({ type: Object }) - entity!: EntityInformation; + cardConfig?: Config; /** * Badge position (reflective attribute) @@ -41,9 +37,6 @@ export class Badge extends HassUpdateMixin(LitElement) { @property({ type: String, reflect: true, attribute: 'position' }) position: string = 'top-right'; - @state() - private _entity?: EntityInformation; - /** * Returns the component's styles */ @@ -52,43 +45,43 @@ export class Badge extends HassUpdateMixin(LitElement) { } /** - * Updates the component's state when Home Assistant state changes - * @param {HomeAssistant} hass - The Home Assistant instance + * Badge configuration */ - // @ts-ignore - override set hass(hass: HomeAssistant) { - this._hass = hass; - - // Determine which entity to use for the badge (defaults to parent entity) - const badgeEntityState = this.config.entity_id - ? getState(this._hass.states, this.config.entity_id) - : this.entity.state; - - // Create badge entity information - this._entity = { - config: this.entity.config, - state: badgeEntityState, - }; + set config(config: BadgeConfig) { + d(this.cardConfig, 'badge', 'set config'); + if (equal(config, this._config)) return; // Set position (convert underscores to hyphens for CSS) - const position = this.config.position ?? 'top_right'; + const position = config.position ?? 'top_right'; this.position = position.replaceAll('_', '-'); + + this.entityId = config.entity_id; + this._config = config; } + /** + * Render the badge + */ public override render(): TemplateResult | typeof nothing { - if (!this._hass || !this._entity?.state) { + d(this.cardConfig, 'badge', 'render'); + + // entity_id is always set (by renderBadgeElements from user config or parent) + const state = this._subscribedEntityState; + const hass = this.hass; + if (!hass || !state) { return nothing; } // For homeassistant mode, use renderTileBadge (HA's native badge helper) - if (this.config.mode === 'homeassistant') { - return renderTileBadge(this._entity.state, this._hass); + const config = this._config; + if (config.mode === 'homeassistant') { + return renderTileBadge(state, hass); } - const matchingState = getMatchingBadgeState(this._entity, this.config); + const matchingState = getMatchingBadgeState(state, config); // For if_match mode, only render if a state match is found - if (this.config.mode === 'if_match' && !matchingState) { + if (config.mode === 'if_match' && !matchingState) { return nothing; } @@ -100,9 +93,9 @@ export class Badge extends HassUpdateMixin(LitElement) { })} > `; diff --git a/src/cards/components/problem/dialog/problem-dialog.ts b/src/cards/components/problem/dialog/problem-dialog.ts index fd4277e3..1d173e89 100644 --- a/src/cards/components/problem/dialog/problem-dialog.ts +++ b/src/cards/components/problem/dialog/problem-dialog.ts @@ -3,7 +3,9 @@ import '@cards/components/problem/list/problem-entity-list'; import { HassUpdateMixin } from '@cards/mixins/hass-update-mixin'; import { fireEvent } from '@hass/common/dom/fire_event'; import type { HassDialog } from '@hass/dialogs/make-dialog-manager'; +import type { Config } from '@type/config'; import type { EntityState } from '@type/room'; +import { d } from '@util/debug'; import { LitElement, html, nothing, type TemplateResult } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { createCloseHeading } from './create-close-heading'; @@ -19,6 +21,11 @@ export class ProblemDialog extends HassUpdateMixin(LitElement) implements HassDialog { + /** + * Card config for debug (optional) + */ + private _config?: Config; + /** * Array of problem entity states */ @@ -36,6 +43,7 @@ export class ProblemDialog */ showDialog(params: ProblemDialogParams): void { this.problemEntities = params.entities; + this._config = params.config; this._opened = true; } @@ -57,6 +65,7 @@ export class ProblemDialog * Renders the component */ override render(): TemplateResult | typeof nothing { + d(this._config, 'problem-dialog', 'render'); if (!this.hass || this.problemEntities.length === 0) { return nothing; } @@ -72,6 +81,7 @@ export class ProblemDialog `; diff --git a/src/cards/components/problem/dialog/show-dialog-problem.ts b/src/cards/components/problem/dialog/show-dialog-problem.ts index 9187ddd4..059032c8 100644 --- a/src/cards/components/problem/dialog/show-dialog-problem.ts +++ b/src/cards/components/problem/dialog/show-dialog-problem.ts @@ -1,8 +1,10 @@ import { fireEvent } from '@hass/common/dom/fire_event'; +import type { Config } from '@type/config'; import type { EntityState } from '@type/room'; export interface ProblemDialogParams { entities: EntityState[]; + config?: Config; } export const loadProblemDialog = () => diff --git a/src/cards/components/problem/list/problem-entity-list.ts b/src/cards/components/problem/list/problem-entity-list.ts index 0f2d0879..d0cc3a00 100644 --- a/src/cards/components/problem/list/problem-entity-list.ts +++ b/src/cards/components/problem/list/problem-entity-list.ts @@ -2,7 +2,9 @@ import '@cards/components/problem/row/problem-entity-row'; import { HassUpdateMixin } from '@cards/mixins/hass-update-mixin'; import { localize } from '@localize/localize'; +import type { Config } from '@type/config'; import type { EntityState } from '@type/room'; +import { d } from '@util/debug'; import { CSSResult, LitElement, html, nothing, type TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; @@ -16,6 +18,11 @@ import { styles } from './styles'; */ @customElement('problem-entity-list') export class ProblemEntityList extends HassUpdateMixin(LitElement) { + /** + * Card config for debug (optional) + */ + config?: Config; + /** * Array of problem entity states */ @@ -26,6 +33,7 @@ export class ProblemEntityList extends HassUpdateMixin(LitElement) { * Renders the component */ override render(): TemplateResult | typeof nothing { + d(this.config, 'problem-entity-list', 'render'); if (!this.hass) { return nothing; } @@ -47,6 +55,7 @@ export class ProblemEntityList extends HassUpdateMixin(LitElement) { `, )} diff --git a/src/cards/components/problem/row/problem-entity-row.ts b/src/cards/components/problem/row/problem-entity-row.ts index 8b59e287..0b797a31 100644 --- a/src/cards/components/problem/row/problem-entity-row.ts +++ b/src/cards/components/problem/row/problem-entity-row.ts @@ -6,7 +6,9 @@ import { stateActive } from '@hass/common/entity/state_active'; import '@hass/state/more-info-mixin'; import { stateDisplay } from '@html/state-display'; import { localize } from '@localize/localize'; +import type { Config } from '@type/config'; import type { EntityState } from '@type/room'; +import { d } from '@util/debug'; import { CSSResult, LitElement, html, nothing, type TemplateResult } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { styles } from './styles'; @@ -19,6 +21,11 @@ import { styles } from './styles'; */ @customElement('problem-entity-row') export class ProblemEntityRow extends HassUpdateMixin(LitElement) { + /** + * Card config for debug (optional) + */ + config?: Config; + /** * The problem entity state */ @@ -38,6 +45,7 @@ export class ProblemEntityRow extends HassUpdateMixin(LitElement) { * Renders the component */ override render(): TemplateResult | typeof nothing { + d(this.config, 'problem-entity-row', 'render'); if (!this.entity || !this.hass) { return nothing; } diff --git a/src/cards/components/room-state-icon/room-state-icon.ts b/src/cards/components/room-state-icon/room-state-icon.ts index be38fe29..b60f8e62 100644 --- a/src/cards/components/room-state-icon/room-state-icon.ts +++ b/src/cards/components/room-state-icon/room-state-icon.ts @@ -145,12 +145,12 @@ export class RoomStateIcon extends HassUpdateMixin(LitElement) { */ // @ts-ignore override set hass(hass: HomeAssistant) { + d(this._config, 'room-state-icon', 'set hass'); this._image = hasEntityFeature(this.entity, 'use_entity_icon') ? undefined : this.entity?.state?.attributes?.entity_picture; if (this._image) { - d(this._config, 'room-state-icon - image', this._image); this.image = true; this.iconBackground = true; this._hideIconContent = true; @@ -160,13 +160,18 @@ export class RoomStateIcon extends HassUpdateMixin(LitElement) { this._hideIconContent = this.isMainRoomEntity ? this._config?.background?.options?.includes('hide_icon_only') || false : false; - this.image = false; + + // regression fix for #383 - in future handle the image logic internally + // but this resets the image to false when the entity_picture is removed for #333 still + this.image = + this._config?.background?.options?.includes('icon_background') ?? false; } this._hass = hass; } public override render(): TemplateResult | typeof nothing { + d(this._config, 'room-state-icon', 'render'); const { state } = this.entity; if (!state) return nothing; @@ -189,8 +194,6 @@ export class RoomStateIcon extends HassUpdateMixin(LitElement) { this._config, ); - d(this._config, 'room-state-icon - iconStyle', iconStyle); - const iconStyles = { ...this._config?.styles?.entity_icon, ...(this.isMainRoomEntity @@ -229,9 +232,9 @@ export class RoomStateIcon extends HassUpdateMixin(LitElement) { // Render badges (max 4) const badgeElements = renderBadgeElements( - this.entity.config.badges, this.entity, this._hass, + this._config, ); return html` diff --git a/src/cards/editor.ts b/src/cards/editor.ts index 282d7d3d..ef4aa782 100644 --- a/src/cards/editor.ts +++ b/src/cards/editor.ts @@ -13,6 +13,7 @@ import type { HASSDomEvent } from '@hass/common/dom/fire_event'; import type { HomeAssistant } from '@hass/types'; import { Task } from '@lit/task'; import type { Config } from '@type/config'; +import { d } from '@util/debug'; import { CSSResult, html, LitElement, nothing, type TemplateResult } from 'lit'; import { state } from 'lit/decorators.js'; import type { Ref } from 'lit/directives/ref.js'; @@ -87,6 +88,7 @@ export class RoomSummaryCardEditor extends LitElement { * @returns The rendered HTML template */ override render() { + d(this._config, 'room-summary-card-editor', 'render'); if (!this.hass || !this._config) { return nothing; } @@ -97,6 +99,7 @@ export class RoomSummaryCardEditor extends LitElement { diff --git a/src/cards/mixins/hass-update-mixin.ts b/src/cards/mixins/hass-update-mixin.ts index efd4d3e8..2081e477 100644 --- a/src/cards/mixins/hass-update-mixin.ts +++ b/src/cards/mixins/hass-update-mixin.ts @@ -1,6 +1,5 @@ import type { HomeAssistant } from '@hass/types'; import type { LitElement } from 'lit'; -import { property } from 'lit/decorators.js'; export interface HassUpdateEvent { hass: HomeAssistant; @@ -16,8 +15,15 @@ export const HassUpdateMixin = >( superClass: T, ) => { class HassUpdateClass extends superClass implements HassUpdateElement { - @property({ attribute: false }) - public hass?: HomeAssistant; + private __hassValue?: HomeAssistant; + + get hass(): HomeAssistant | undefined { + return this.__hassValue; + } + + set hass(value: HomeAssistant | undefined) { + this.__hassValue = value; + } private readonly _boundHassUpdateHandler = this._handleHassUpdate.bind(this); @@ -29,7 +35,10 @@ export const HassUpdateMixin = >( override disconnectedCallback(): void { super.disconnectedCallback(); - globalThis.removeEventListener('hass-update', this._boundHassUpdateHandler); + globalThis.removeEventListener( + 'hass-update', + this._boundHassUpdateHandler, + ); } private _handleHassUpdate(event: Event): void { diff --git a/src/cards/mixins/subscribe-entity-state-mixin.ts b/src/cards/mixins/subscribe-entity-state-mixin.ts new file mode 100644 index 00000000..429223fb --- /dev/null +++ b/src/cards/mixins/subscribe-entity-state-mixin.ts @@ -0,0 +1,118 @@ +import { subscribeEntityState } from '@delegates/entities/subscribe-trigger'; +import { getState } from '@delegates/retrievers/state'; +import type { HomeAssistant } from '@hass/types'; +import type { SubscriptionUnsubscribe } from '@hass/ws/types'; +import type { EntityState } from '@type/room'; +import type { LitElement } from 'lit'; +import { state } from 'lit/decorators.js'; +const equal = require('fast-deep-equal'); + +export type Constructor = new (...args: any[]) => T; + +export interface SubscribeEntityStateElement { + hass?: HomeAssistant; +} + +/** + * Mixin that subscribes to entity state changes via subscribe_trigger. + * Set entityIdToSubscribe to specify which entity to watch. + * Read _subscribedEntityState for the current state (undefined when not subscribed). + */ +export const SubscribeEntityStateMixin = < + T extends Constructor, +>( + superClass: T, +) => { + class SubscribeEntityStateClass extends superClass { + /** + * The unsubscribe function for the subscription. + */ + private _unsubscribe?: SubscriptionUnsubscribe; + + /** + * The entity_id of the subscribed entity. + */ + private _subscribedEntityId?: string; + + /** + * The entity_id to subscribe to. Set this property to specify which entity to watch. + */ + protected entityId?: string; + + /** + * The current state of the subscribed entity. + * Updates cause re-render of the component. + */ + @state() + protected _subscribedEntityState?: EntityState; + + /** + * Setup the entity subscription. + */ + override connectedCallback(): void { + super.connectedCallback(); + this._setupEntitySubscription(); + } + + /** + * Teardown the entity subscription. + */ + override disconnectedCallback(): void { + this._teardownEntitySubscription(); + super.disconnectedCallback(); + } + + /** + * Teardown the entity subscription. + */ + private _teardownEntitySubscription(): void { + if (this._unsubscribe) { + this._unsubscribe(); + this._unsubscribe = undefined; + this._subscribedEntityId = undefined; + } + } + + /** + * Setup the entity subscription. + */ + private _setupEntitySubscription(): void { + const id = this.entityId; + const hass = this.hass; + + if (!id || !hass) { + this._teardownEntitySubscription(); + this._subscribedEntityState = undefined; + return; + } + + if (this._subscribedEntityId === id) { + return; + } + + this._teardownEntitySubscription(); + this._subscribedEntityId = id; + + const initialState = getState(hass.states, id); + this._subscribedEntityState = initialState; + + subscribeEntityState( + hass, + id, + ({ variables: { trigger: { from_state, to_state } = {} } = {} }) => { + // use getState since it cuts out extra properties like timestamps + const fromState = getState({ [id]: from_state }, id); + const toState = getState({ [id]: to_state }, id); + if (equal(fromState, toState)) return; + + // Update the state if it has changed. + this._subscribedEntityState = toState; + }, + ).then((unsubscribe) => { + this._unsubscribe = unsubscribe; + }); + } + } + + return SubscribeEntityStateClass; +}; diff --git a/src/delegates/checks/occupancy.ts b/src/delegates/checks/occupancy.ts index 575eae17..98a027b6 100644 --- a/src/delegates/checks/occupancy.ts +++ b/src/delegates/checks/occupancy.ts @@ -1,5 +1,6 @@ import { stateActive } from '@hass/common/entity/state_active'; import type { HomeAssistant } from '@hass/types'; +import { processHomeAssistantColors } from '@theme/colors'; import type { AlarmConfig } from '@type/config'; /** @@ -52,7 +53,8 @@ const getAlarmCssVars = ( // Set card border variable (3px solid) unless disabled const isCardBorderDisabled = config.options?.includes('disabled_card_styles'); if (!isCardBorderDisabled) { - const borderColor = config.card_border_color ?? defaultColor; + const borderColor = + processHomeAssistantColors(config.card_border_color) ?? defaultColor; vars[`--${prefix}-card-border`] = `3px solid ${borderColor}`; vars[`--${prefix}-card-border-color`] = borderColor; @@ -69,7 +71,8 @@ const getAlarmCssVars = ( // Icon color styling const isIconColorDisabled = config.options?.includes('disable_icon_styles'); if (!isIconColorDisabled) { - const iconColor = config.icon_color ?? defaultColor; + const iconColor = + processHomeAssistantColors(config.icon_color) ?? defaultColor; vars[`--${prefix}-icon-color`] = iconColor; // Set animation unless disabled diff --git a/src/delegates/entities/subscribe-trigger.ts b/src/delegates/entities/subscribe-trigger.ts new file mode 100644 index 00000000..ae0b7872 --- /dev/null +++ b/src/delegates/entities/subscribe-trigger.ts @@ -0,0 +1,28 @@ +/** + * Subscribe to entity state changes via Home Assistant's subscribe_trigger API. + * Only receives events when the specified entity changes (server-side filtering). + * + * @see https://developers.home-assistant.io/docs/api/websocket#subscribe_trigger + */ + +import type { HomeAssistant } from '@hass/types'; +import type { SubscriptionUnsubscribe } from '@hass/ws/types'; + +export interface StateTriggerResult { + variables: { trigger: Record }; + context: { id: string; parent_id: string | null; user_id: string | null }; +} + +/** + * Subscribe to state changes for a single entity. + * Returns a Promise that resolves to the unsubscribe function. + */ +export const subscribeEntityState = ( + hass: HomeAssistant, + entityId: string, + onChange: (result: StateTriggerResult) => void, +): Promise => + hass.connection.subscribeMessage(onChange, { + type: 'subscribe_trigger', + trigger: { platform: 'state', entity_id: entityId }, + }); diff --git a/src/delegates/utils/badge-state.ts b/src/delegates/utils/badge-state.ts index 9ba953ce..42e0d1af 100644 --- a/src/delegates/utils/badge-state.ts +++ b/src/delegates/utils/badge-state.ts @@ -1,20 +1,18 @@ -import { meetsStateCondition } from '@util/comparison-utils'; import type { BadgeConfig, StateConfig } from '@type/config/entity'; -import type { EntityInformation } from '@type/room'; +import type { EntityState } from '@type/room'; +import { meetsStateCondition } from '@util/comparison-utils'; /** * Gets the matching badge state configuration for a badge * - * @param entity - The entity information containing state + * @param state - The entity state * @param badge - The badge configuration * @returns The matching StateConfig if found, undefined otherwise */ export const getMatchingBadgeState = ( - entity: EntityInformation, + state: EntityState, badge: BadgeConfig, ): StateConfig | undefined => { - const { state } = entity; - if (!badge.states || !state) { return undefined; } diff --git a/src/delegates/utils/hide-yo-sensors.ts b/src/delegates/utils/hide-yo-sensors.ts index ecf13355..ef275a96 100644 --- a/src/delegates/utils/hide-yo-sensors.ts +++ b/src/delegates/utils/hide-yo-sensors.ts @@ -9,6 +9,7 @@ import type { SensorConfig } from '@type/config/sensor'; import type { EntityState } from '@type/room'; import type { SensorData } from '@type/sensor'; import { calculateAverages } from './sensor-averages'; +import { probablyClassSensorUsersMadeThisComplex } from './sensor-sifter'; /** * Helper function to extract entity_id from LightConfig @@ -41,14 +42,11 @@ const isAmbientLight = (light: LightConfig): boolean => { * @returns SensorData object containing individual and averaged sensor information, plus light entities. */ export const getSensors = (hass: HomeAssistant, config: Config): SensorData => { - const skipDefaultEntities = hasFeature(config, 'exclude_default_entities'); const multiLightEnabled = hasFeature(config, 'multi_light_background'); const hideHiddenEntities = hasFeature(config, 'hide_hidden_entities'); // Get area information to check for configured temperature/humidity sensors const area = getArea(hass.areas, config.area); - const areaHasTemperatureSensor = !!area?.temperature_entity_id; - const areaHasHumiditySensor = !!area?.humidity_entity_id; // Default sensor classes if not specified const sensorClasses = config.sensor_classes || [ @@ -138,18 +136,6 @@ export const getSensors = (hass: HomeAssistant, config: Config): SensorData => { } }; - // Helper function to check if entity is a class sensor - const isClassSensor = ( - state: EntityState, - deviceClass: string | undefined, - ): boolean => { - return ( - state.domain === 'sensor' && - !!deviceClass && - sensorClasses.includes(deviceClass) - ); - }; - // Process all entities in the area Object.values(hass.entities).forEach((entity) => { // Skip hidden entities if the feature is enabled @@ -214,39 +200,15 @@ export const getSensors = (hass: HomeAssistant, config: Config): SensorData => { return; } - // If we're skipping default entities, don't process further - if (skipDefaultEntities) return; - - // Check if this is the area's default temperature/humidity sensor - const isAreaDefaultTemp = - areaHasTemperatureSensor && - entity.entity_id === area.temperature_entity_id; - const isAreaDefaultHumidity = - areaHasHumiditySensor && entity.entity_id === area.humidity_entity_id; - - // If this is an area default sensor, add it to classSensors (unless already configured) + // Check if this entity qualifies as a class-based sensor for averaging if ( - (isAreaDefaultTemp || isAreaDefaultHumidity) && - !isConfigSensor && - !isThresholdEntity + probablyClassSensorUsersMadeThisComplex( + state, + config, + area, + sensorClasses, + ) ) { - const deviceClass = state.attributes?.device_class; - if (isClassSensor(state, deviceClass)) { - classSensors.push(state); - } - return; - } - - // Check if this is a sensor with a device class we care about - const deviceClass = state.attributes?.device_class; - if (isClassSensor(state, deviceClass)) { - // Skip temperature/humidity sensors if the area has configured defaults - if ( - (deviceClass === 'temperature' && areaHasTemperatureSensor) || - (deviceClass === 'humidity' && areaHasHumiditySensor) - ) { - return; - } classSensors.push(state); } }); diff --git a/src/delegates/utils/sensor-sifter.ts b/src/delegates/utils/sensor-sifter.ts new file mode 100644 index 00000000..95f86bbb --- /dev/null +++ b/src/delegates/utils/sensor-sifter.ts @@ -0,0 +1,51 @@ +import { hasFeature } from '@config/feature'; +import type { AreaRegistryEntry } from '@hass/data/area/area_registry'; +import type { Config } from '@type/config'; +import type { EntityState } from '@type/room'; + +/** + * Determines whether an entity should be collected as a class-based sensor + * for averaging. Handles area-default sensor logic: when an area has a + * configured default temperature/humidity sensor, only that specific sensor + * is included and other sensors of the same class are skipped. + */ +export const probablyClassSensorUsersMadeThisComplex = ( + state: EntityState, + config: Config, + area: AreaRegistryEntry | undefined, + sensorClasses: string[], +): boolean => { + if (hasFeature(config, 'exclude_default_entities')) return false; + + const deviceClass = state.attributes?.device_class; + if ( + state.domain !== 'sensor' || + !deviceClass || + !sensorClasses.includes(deviceClass) + ) { + return false; + } + + const areaHasTemp = !!area?.temperature_entity_id; + const areaHasHumidity = !!area?.humidity_entity_id; + + const isAreaDefaultTemp = + areaHasTemp && state.entity_id === area?.temperature_entity_id; + const isAreaDefaultHumidity = + areaHasHumidity && state.entity_id === area?.humidity_entity_id; + + // Area default sensors are always included + if (isAreaDefaultTemp || isAreaDefaultHumidity) { + return true; + } + + // Skip non-default temp/humidity sensors when area has configured defaults + if ( + (deviceClass === 'temperature' && areaHasTemp) || + (deviceClass === 'humidity' && areaHasHumidity) + ) { + return false; + } + + return true; +}; diff --git a/src/hass/types.ts b/src/hass/types.ts index 50d25136..2c0ebbb1 100644 --- a/src/hass/types.ts +++ b/src/hass/types.ts @@ -8,6 +8,7 @@ import type { DeviceRegistryEntry } from './data/device_registry'; import type { EntityRegistryDisplayEntry } from './data/entity_registry'; import type { Themes } from './data/ws-themes'; import type { + Connection, HassEntities, HassEntity, HassServiceTarget, @@ -33,6 +34,7 @@ export interface ServiceCallRequest { } export interface HomeAssistant { + connection: Connection; states: HassEntities; entities: Record; devices: Record; diff --git a/src/hass/ws/types.ts b/src/hass/ws/types.ts index 70e84b2a..bac15aeb 100644 --- a/src/hass/ws/types.ts +++ b/src/hass/ws/types.ts @@ -2,6 +2,8 @@ * https://github.com/home-assistant/home-assistant-js-websocket/blob/master/lib/types.ts */ +export type SubscriptionUnsubscribe = () => Promise; + export type MessageBase = { id?: number; type: string; @@ -40,3 +42,14 @@ export type HassServiceTarget = { floor_id?: string | string[]; label_id?: string | string[]; }; + +export interface Connection { + subscribeMessage( + callback: (result: Result) => void, + subscribeMessage: MessageBase, + options?: { + resubscribe?: boolean; + preCheck?: () => boolean | Promise; + }, + ): Promise; +} diff --git a/src/html/badge-squad.ts b/src/html/badge-squad.ts index 9529e008..8e512601 100644 --- a/src/html/badge-squad.ts +++ b/src/html/badge-squad.ts @@ -1,26 +1,32 @@ import type { HomeAssistant } from '@hass/types'; -import type { BadgeConfig } from '@type/config/entity'; +import type { Config } from '@type/config'; import type { EntityInformation } from '@type/room'; import { html, type TemplateResult } from 'lit'; /** * Renders badge elements for an entity. * Limits badges to a maximum of 4. + * Ensures each badge has entity_id (from user config or parent entity). * - * @param badges - Array of badge configurations (will be limited to first 4) - * @param entity - The entity information + * @param entity - The entity information (used for entity_id and entityConfig) * @param hass - Home Assistant instance + * @param config - Card config for debug (optional) * @returns Array of badge template results */ export const renderBadgeElements = ( - badges: BadgeConfig[] | undefined, entity: EntityInformation, hass: HomeAssistant, -): TemplateResult[] => { - const badgeConfigs = badges?.slice(0, 4) ?? []; - return badgeConfigs.map( - (badge) => html` - - `, - ); -}; + config?: Config, +): TemplateResult[] | undefined => + entity.config.badges?.slice(0, 4)?.map((badge) => { + return html` + + `; + }); diff --git a/src/html/icon.ts b/src/html/icon.ts index 80e96e86..af4f51dd 100644 --- a/src/html/icon.ts +++ b/src/html/icon.ts @@ -42,7 +42,10 @@ export const renderProblemIndicator = ( class="status-entities" ?has-problems=${problemExists} @click=${() => - showProblemDialog(element, { entities: problemSensors })} + showProblemDialog(element, { + entities: problemSensors, + config, + })} >${problemSensors.length}` : nothing} diff --git a/src/types/config/index.ts b/src/types/config/index.ts index 8b167556..3dfa04bd 100644 --- a/src/types/config/index.ts +++ b/src/types/config/index.ts @@ -135,11 +135,19 @@ export interface Config { /** Options to enable or disable features **/ features?: Features[]; + + /** Debug configuration. When present, enables scoped debug logging to console. */ + debug?: { + /** Only log for these components (e.g. ['room-summary-card']). Omit = all components */ + scope?: string[]; + + /** Only log these categories (e.g. ['render']). Omit = all categories */ + categories?: string[]; + }; } /** Features to enable or disable functionality */ export type Features = - | 'debug' | 'hide_area_stats' | 'hide_climate_label' | 'hide_room_icon' diff --git a/src/util/debug.ts b/src/util/debug.ts index acb2b055..75cdf223 100644 --- a/src/util/debug.ts +++ b/src/util/debug.ts @@ -1,14 +1,25 @@ -import { hasFeature } from '@config/feature'; import type { Config } from '@type/config'; /** - * Logs debug messages to the console if the 'debug' feature is enabled in the configuration. + * Logs debug messages to the console when config.debug is present. + * Supports scoping by component and/or category. * - * @param config - The configuration object used to determine if debugging is enabled. - * @param args - The arguments to be logged to the console. + * @param config - The configuration object. When undefined, no logging occurs. + * @param component - Component name (e.g. 'room-summary-card') + * @param category - Category (e.g. 'render', 'set hass') + * @param args - Additional data to log */ -export const d = (config: Config, ...args: any[]) => { - if (hasFeature(config, 'debug')) { - console.debug(...args); - } +export const d = ( + config: Config | undefined, + component: string, + category: string, + ...args: any[] +) => { + if (!config?.debug) return; + + const { scope, categories } = config.debug; + if (scope?.length && !scope.includes(component)) return; + if (categories?.length && !categories.includes(category)) return; + + console.debug(`[${component}] ${category}`, ...args); }; diff --git a/test/cards/components/badge/badge.spec.ts b/test/cards/components/badge/badge.spec.ts index 24855c1d..3229caec 100644 --- a/test/cards/components/badge/badge.spec.ts +++ b/test/cards/components/badge/badge.spec.ts @@ -1,18 +1,13 @@ import { Badge } from '@cards/components/badge/badge'; import { styles } from '@cards/components/badge/styles'; -import * as stateRetrieverModule from '@delegates/retrievers/state'; import * as badgeStateModule from '@delegates/utils/badge-state'; import * as renderTileBadgeModule from '@hass/panels/lovelace/cards/tile/badges/tile-badge'; import type { HomeAssistant } from '@hass/types'; import { fixture } from '@open-wc/testing-helpers'; import { createStateEntity } from '@test/test-helpers'; import * as styleConverterModule from '@theme/util/style-converter'; -import type { - BadgeConfig, - EntityConfig, - StateConfig, -} from '@type/config/entity'; -import type { EntityInformation, EntityState } from '@type/room'; +import type { BadgeConfig, StateConfig } from '@type/config/entity'; +import type { EntityState } from '@type/room'; import { expect } from 'chai'; import { html, nothing, type TemplateResult } from 'lit'; import { stub } from 'sinon'; @@ -20,44 +15,22 @@ import { stub } from 'sinon'; describe('badge.ts', () => { let element: Badge; let mockHass: HomeAssistant; - let getStateStub: sinon.SinonStub; + let mockEntityState: EntityState; let getMatchingBadgeStateStub: sinon.SinonStub; let renderTileBadgeStub: sinon.SinonStub; let stylesToHostCssStub: sinon.SinonStub; - const mockEntityState: EntityState = createStateEntity( - 'light', - 'living_room', - 'on', - { - friendly_name: 'Living Room Light', - }, - ); - - const mockEntityConfig: EntityConfig = { - entity_id: 'light.living_room', - icon: 'mdi:lightbulb', - }; - - const mockEntity: EntityInformation = { - config: mockEntityConfig, - state: mockEntityState, - }; - const mockBadgeConfig: BadgeConfig = { + entity_id: 'light.living_room', position: 'top_right', mode: 'show_always', }; beforeEach(() => { - // Register custom element if not already registered - if (!customElements.get('room-summary-badge')) { - customElements.define('room-summary-badge', Badge); - } + mockEntityState = createStateEntity('light', 'living_room', 'on', { + friendly_name: 'Living Room Light', + }); - getStateStub = stub(stateRetrieverModule, 'getState').returns( - mockEntityState, - ); getMatchingBadgeStateStub = stub( badgeStateModule, 'getMatchingBadgeState', @@ -79,20 +52,20 @@ describe('badge.ts', () => { element = new Badge(); element.config = mockBadgeConfig; - element.entity = mockEntity; + element.hass = mockHass; + // Badge gets state from SubscribeEntityStateMixin; set directly for unit tests + element['_subscribedEntityState'] = mockEntityState; }); afterEach(() => { - getStateStub.restore(); getMatchingBadgeStateStub.restore(); renderTileBadgeStub.restore(); stylesToHostCssStub.restore(); }); - describe('properties', () => { - it('should have correct property types', () => { - expect(element.config).to.equal(mockBadgeConfig); - expect(element.entity).to.equal(mockEntity); + describe('config and properties', () => { + it('should store config and set position from config', () => { + expect(element['_config']).to.equal(mockBadgeConfig); expect(element.position).to.equal('top-right'); }); @@ -104,124 +77,41 @@ describe('badge.ts', () => { const newElement = new Badge(); expect(newElement.position).to.equal('top-right'); }); - }); - - describe('hass setter', () => { - it('should set internal hass and update entity', () => { - element.hass = mockHass; - - expect(element['_hass']).to.equal(mockHass); - expect(element['_entity']).to.exist; - expect(element['_entity']?.config).to.equal(mockEntityConfig); - expect(element['_entity']?.state).to.equal(mockEntityState); - }); - it('should use parent entity when config.entity_id is not set', () => { - element.config = { - ...mockBadgeConfig, - entity_id: undefined, - }; - element.hass = mockHass; - - expect(getStateStub.called).to.be.false; - expect(element['_entity']?.state).to.equal(mockEntityState); - }); - - it('should use config.entity_id when set', () => { - const otherEntityState = createStateEntity( - 'sensor', - 'temperature', - '25', - { - unit_of_measurement: '°C', - }, - ); - getStateStub.returns(otherEntityState); - - element.config = { - ...mockBadgeConfig, - entity_id: 'sensor.temperature', - }; - element.hass = mockHass; - - expect(getStateStub.calledWith(mockHass.states, 'sensor.temperature')).to - .be.true; - expect(element['_entity']?.state).to.equal(otherEntityState); - expect(element['_entity']?.config).to.equal(mockEntityConfig); + it('should set entityId from config.entity_id', () => { + element.config = { ...mockBadgeConfig, entity_id: 'sensor.temp' }; + expect(element['entityId']).to.equal('sensor.temp'); }); it('should convert position underscores to hyphens', () => { - element.config = { - ...mockBadgeConfig, - position: 'top_left', - }; - element.hass = mockHass; - + element.config = { ...mockBadgeConfig, position: 'top_left' }; expect(element.position).to.equal('top-left'); }); - - it('should default to top_right position when not specified', () => { - element.config = { - mode: 'show_always', - }; - element.hass = mockHass; - - expect(element.position).to.equal('top-right'); - }); - - it('should handle all position values', () => { - const positions: Array< - 'top_right' | 'top_left' | 'bottom_right' | 'bottom_left' - > = ['top_right', 'top_left', 'bottom_right', 'bottom_left']; - - positions.forEach((pos) => { - element.config = { - ...mockBadgeConfig, - position: pos, - }; - element.hass = mockHass; - expect(element.position).to.equal(pos.replace(/_/g, '-')); - }); - }); }); describe('render', () => { - beforeEach(() => { - element.hass = mockHass; - }); - - it('should render nothing when entity is not set', () => { - element['_entity'] = undefined; + it('should render nothing when hass is not set', () => { + element.hass = undefined as any; expect(element.render()).to.equal(nothing); }); - it('should render nothing when hass is not set', () => { - element['_hass'] = undefined as any; + it('should render nothing when _subscribedEntityState is not set', () => { + element['_subscribedEntityState'] = undefined; expect(element.render()).to.equal(nothing); }); describe('homeassistant mode', () => { it('should use renderTileBadge when mode is homeassistant', () => { - element.config = { - ...mockBadgeConfig, - mode: 'homeassistant', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'homeassistant' }; const result = element.render(); expect(result).to.not.equal(nothing); - expect(renderTileBadgeStub.called).to.be.true; expect(renderTileBadgeStub.calledWith(mockEntityState, mockHass)).to.be .true; }); it('should not call getMatchingBadgeState in homeassistant mode', () => { - element.config = { - ...mockBadgeConfig, - mode: 'homeassistant', - }; - element.hass = mockHass; - + element.config = { ...mockBadgeConfig, mode: 'homeassistant' }; element.render(); expect(getMatchingBadgeStateStub.called).to.be.false; }); @@ -230,15 +120,16 @@ describe('badge.ts', () => { describe('if_match mode', () => { it('should render nothing when no matching state is found', () => { getMatchingBadgeStateStub.returns(undefined); - element.config = { - ...mockBadgeConfig, - mode: 'if_match', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'if_match' }; const result = element.render(); expect(result).to.equal(nothing); - expect(getMatchingBadgeStateStub.called).to.be.true; + expect( + getMatchingBadgeStateStub.calledWith( + mockEntityState, + element['_config'], + ), + ).to.be.true; }); it('should render badge when matching state is found', () => { @@ -248,27 +139,17 @@ describe('badge.ts', () => { icon: 'mdi:light-on', }; getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'if_match', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'if_match' }; const result = element.render(); expect(result).to.not.equal(nothing); - expect(getMatchingBadgeStateStub.called).to.be.true; }); }); describe('show_always mode', () => { it('should render badge even when no matching state', () => { getMatchingBadgeStateStub.returns(undefined); - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'show_always' }; const result = element.render(); expect(result).to.not.equal(nothing); @@ -281,24 +162,7 @@ describe('badge.ts', () => { icon: 'mdi:light-on', }; getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; - - const result = element.render(); - expect(result).to.not.equal(nothing); - }); - }); - - describe('default mode (show_always)', () => { - it('should render badge when mode is not specified', () => { - element.config = { - position: 'top_right', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'show_always' }; const result = element.render(); expect(result).to.not.equal(nothing); @@ -306,24 +170,18 @@ describe('badge.ts', () => { }); describe('badge rendering', () => { - it('should render ha-tile-badge with correct properties', async () => { + it('should render ha-tile-badge with icon_color from matching state', async () => { const matchingState: StateConfig = { state: 'on', icon_color: 'yellow', icon: 'mdi:light-on', }; getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'show_always' }; const result = element.render() as TemplateResult; const el = await fixture(result); - // fixture() may return the root element or wrap it in a container const tileBadge = ( el.tagName.toLowerCase() === 'ha-tile-badge' ? el @@ -342,12 +200,7 @@ describe('badge.ts', () => { icon: 'mdi:light-on', }; getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'show_always' }; const result = element.render() as TemplateResult; const el = await fixture(result); @@ -358,42 +211,6 @@ describe('badge.ts', () => { expect((stateIcon as any).stateObj).to.equal(mockEntityState); expect((stateIcon as any).icon).to.equal('mdi:light-on'); }); - - it('should use entity config icon when matching state has no icon', async () => { - const matchingState: StateConfig = { - state: 'on', - icon_color: 'yellow', - }; - getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; - - const result = element.render() as TemplateResult; - const el = await fixture(result); - - const stateIcon = el.querySelector('ha-state-icon'); - expect((stateIcon as any).icon).to.equal('mdi:lightbulb'); - }); - - it('should use entity config icon when no matching state', async () => { - getMatchingBadgeStateStub.returns(undefined); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; - - const result = element.render() as TemplateResult; - const el = await fixture(result); - - const stateIcon = el.querySelector('ha-state-icon'); - expect((stateIcon as any).icon).to.equal('mdi:lightbulb'); - }); }); describe('styles handling', () => { @@ -402,21 +219,13 @@ describe('badge.ts', () => { state: 'on', icon_color: 'yellow', icon: 'mdi:light-on', - styles: { - '--custom-property': 'value', - }, + styles: { '--custom-property': 'value' }, }; getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'show_always' }; element.render(); - expect(stylesToHostCssStub.called).to.be.true; expect(stylesToHostCssStub.calledWith(matchingState.styles)).to.be.true; }); @@ -426,200 +235,24 @@ describe('badge.ts', () => { icon_color: 'yellow', }; getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; - - element.render(); - - expect(stylesToHostCssStub.called).to.be.false; - }); - - it('should not call stylesToHostCss when no matching state', () => { - getMatchingBadgeStateStub.returns(undefined); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; + element.config = { ...mockBadgeConfig, mode: 'show_always' }; element.render(); expect(stylesToHostCssStub.called).to.be.false; }); }); - - describe('icon color handling', () => { - it('should set background color from matching state', async () => { - const matchingState: StateConfig = { - state: 'on', - icon_color: 'red', - }; - getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; - - const result = element.render() as TemplateResult; - const el = await fixture(result); - - // fixture() may return the root element or wrap it in a container - const tileBadge = ( - el.tagName.toLowerCase() === 'ha-tile-badge' - ? el - : el.querySelector('ha-tile-badge') - ) as HTMLElement; - expect( - tileBadge?.style.getPropertyValue('--tile-badge-background-color'), - ).to.equal('red'); - }); - - it('should handle undefined icon_color gracefully', async () => { - const matchingState: StateConfig = { - state: 'on', - }; - getMatchingBadgeStateStub.returns(matchingState); - - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; - - const result = element.render() as TemplateResult; - const el = await fixture(result); - - // fixture() may return the root element or wrap it in a container - const tileBadge = - el.tagName.toLowerCase() === 'ha-tile-badge' - ? el - : el.querySelector('ha-tile-badge'); - expect(tileBadge).to.exist; - // Should still render even without icon_color - }); - }); }); - describe('edge cases', () => { - beforeEach(() => { - element.hass = mockHass; - }); - - it('should handle entity with undefined state', () => { - element.entity = { - ...mockEntity, - state: undefined, - }; - element.hass = mockHass; - - // Should still set _entity but render will return nothing - expect(element['_entity']?.state).to.be.undefined; - expect(element.render()).to.equal(nothing); - }); - - it('should handle config without mode', () => { - element.config = { - position: 'top_right', - }; - element.hass = mockHass; - - const result = element.render(); - expect(result).to.not.equal(nothing); - }); - - it('should handle config without position', () => { - element.config = { - mode: 'show_always', - }; - element.hass = mockHass; - - expect(element.position).to.equal('top-right'); - const result = element.render(); - expect(result).to.not.equal(nothing); - }); - - it('should handle entity config without icon', () => { - const entityWithoutIcon: EntityInformation = { - config: { - entity_id: 'light.living_room', - }, - state: mockEntityState, - }; - element.entity = entityWithoutIcon; - element.hass = mockHass; - - const result = element.render() as TemplateResult; - expect(result).to.not.equal(nothing); - }); - - it('should handle getState returning undefined for custom entity_id', () => { - getStateStub.returns(undefined); - - element.config = { - ...mockBadgeConfig, - entity_id: 'sensor.nonexistent', - }; - element.hass = mockHass; - - // Should use undefined state from getState - expect(element['_entity']?.state).to.be.undefined; - expect(element.render()).to.equal(nothing); - }); - }); - - describe('integration', () => { - beforeEach(() => { - element.hass = mockHass; - }); - - it('should call getMatchingBadgeState with correct parameters', () => { - element.config = { - ...mockBadgeConfig, - mode: 'show_always', - }; - element.hass = mockHass; - - element.render(); - - expect(getMatchingBadgeStateStub.called).to.be.true; - expect( - getMatchingBadgeStateStub.calledWith( - element['_entity'], - mockBadgeConfig, - ), - ).to.be.true; - }); - - it('should use custom entity when config.entity_id is set', () => { - const customEntityState = createStateEntity( - 'sensor', - 'temperature', - '25', - ); - getStateStub.returns(customEntityState); - - element.config = { - ...mockBadgeConfig, - entity_id: 'sensor.temperature', - }; - element.hass = mockHass; - + describe('getMatchingBadgeState integration', () => { + it('should call getMatchingBadgeState with state and config', () => { + element.config = { ...mockBadgeConfig, mode: 'show_always' }; element.render(); expect( getMatchingBadgeStateStub.calledWith( - { - config: mockEntityConfig, - state: customEntityState, - }, - element.config, + mockEntityState, + element['_config'], ), ).to.be.true; }); diff --git a/test/cards/components/room-state-icon.spec.ts b/test/cards/components/room-state-icon.spec.ts index ccb61a5f..5f19b74f 100644 --- a/test/cards/components/room-state-icon.spec.ts +++ b/test/cards/components/room-state-icon.spec.ts @@ -313,13 +313,8 @@ describe('room-state-icon.ts', () => { element.render(); expect(renderBadgeElementsStub.called).to.be.true; - expect( - renderBadgeElementsStub.calledWith( - mockEntity.config.badges, - mockEntity, - mockHass, - ), - ).to.be.true; + expect(renderBadgeElementsStub.calledWith(mockEntity, mockHass)).to.be + .true; }); it('should show entity label when show_entity_labels feature is enabled', async () => { diff --git a/test/cards/mixins/hass-update-mixin.spec.ts b/test/cards/mixins/hass-update-mixin.spec.ts index dd1fd9af..01a941d0 100644 --- a/test/cards/mixins/hass-update-mixin.spec.ts +++ b/test/cards/mixins/hass-update-mixin.spec.ts @@ -27,12 +27,6 @@ describe('HassUpdateMixin', () => { } as HomeAssistant; }); - afterEach(() => { - if (element.parentNode) { - element.parentNode.removeChild(element); - } - }); - it('should have hass property', () => { expect(element).to.have.property('hass'); expect(element.hass).to.be.undefined; diff --git a/test/cards/mixins/subscribe-entity-state-mixin.spec.ts b/test/cards/mixins/subscribe-entity-state-mixin.spec.ts new file mode 100644 index 00000000..6d21021a --- /dev/null +++ b/test/cards/mixins/subscribe-entity-state-mixin.spec.ts @@ -0,0 +1,167 @@ +import { SubscribeEntityStateMixin } from '@cards/mixins/subscribe-entity-state-mixin'; +import * as subscribeTrigger from '@delegates/entities/subscribe-trigger'; +import type { HomeAssistant } from '@hass/types'; +import { expect } from 'chai'; +import { LitElement } from 'lit'; +import { type SinonStub, stub } from 'sinon'; + +describe('SubscribeEntityStateMixin', () => { + let TestElement: ReturnType; + let element: InstanceType; + let hass: HomeAssistant; + let subscribeStub: SinonStub; + let unsubscribeSpy: SinonStub; + let elementCounter = 0; + + beforeEach(() => { + unsubscribeSpy = stub(); + subscribeStub = stub(subscribeTrigger, 'subscribeEntityState').resolves( + unsubscribeSpy, + ); + + const elementName = `test-sub-entity-${elementCounter++}`; + TestElement = SubscribeEntityStateMixin(LitElement); + + if (!customElements.get(elementName)) { + customElements.define(elementName, TestElement); + } + + element = new TestElement(); + hass = { + language: 'en', + localize: (key: string) => key, + states: { + 'light.bedroom': { + entity_id: 'light.bedroom', + state: 'on', + attributes: { friendly_name: 'Bedroom Light' }, + }, + }, + } as unknown as HomeAssistant; + }); + + afterEach(() => { + subscribeStub.restore(); + }); + + it('should have _subscribedEntityState undefined initially', () => { + expect(element['_subscribedEntityState']).to.be.undefined; + }); + + it('should subscribe when connected with entityId and hass set', async () => { + element.hass = hass; + element['entityId'] = 'light.bedroom'; + + element.connectedCallback(); + + expect(subscribeStub.calledOnce).to.be.true; + expect(subscribeStub.firstCall.args[1]).to.equal('light.bedroom'); + }); + + it('should set initial state from hass.states on subscribe', () => { + element.hass = hass; + element['entityId'] = 'light.bedroom'; + + element.connectedCallback(); + + expect(element['_subscribedEntityState']).to.deep.equal({ + entity_id: 'light.bedroom', + state: 'on', + attributes: { friendly_name: 'Bedroom Light' }, + domain: 'light', + }); + }); + + it('should not subscribe without entityId', () => { + element.hass = hass; + + element.connectedCallback(); + + expect(subscribeStub.called).to.be.false; + }); + + it('should not subscribe without hass', () => { + element['entityId'] = 'light.bedroom'; + + element.connectedCallback(); + + expect(subscribeStub.called).to.be.false; + }); + + it('should unsubscribe on disconnectedCallback', async () => { + element.hass = hass; + element['entityId'] = 'light.bedroom'; + + element.connectedCallback(); + + // Wait for the subscribe promise to resolve + await Promise.resolve(); + + element.disconnectedCallback(); + + expect(unsubscribeSpy.calledOnce).to.be.true; + }); + + it('should update state when trigger callback fires with changed state', () => { + element.hass = hass; + element['entityId'] = 'light.bedroom'; + + element.connectedCallback(); + + // Get the onChange callback passed to subscribeEntityState + const onChange = subscribeStub.firstCall.args[2]; + + onChange({ + variables: { + trigger: { + from_state: { + entity_id: 'light.bedroom', + state: 'on', + attributes: { friendly_name: 'Bedroom Light' }, + }, + to_state: { + entity_id: 'light.bedroom', + state: 'off', + attributes: { friendly_name: 'Bedroom Light' }, + }, + }, + }, + }); + + expect(element['_subscribedEntityState']).to.deep.equal({ + entity_id: 'light.bedroom', + state: 'off', + attributes: { friendly_name: 'Bedroom Light' }, + domain: 'light', + }); + }); + + it('should not update state when from_state equals to_state', () => { + element.hass = hass; + element['entityId'] = 'light.bedroom'; + + element.connectedCallback(); + + const initialState = element['_subscribedEntityState']; + const onChange = subscribeStub.firstCall.args[2]; + + onChange({ + variables: { + trigger: { + from_state: { + entity_id: 'light.bedroom', + state: 'on', + attributes: { friendly_name: 'Bedroom Light' }, + }, + to_state: { + entity_id: 'light.bedroom', + state: 'on', + attributes: { friendly_name: 'Bedroom Light' }, + }, + }, + }, + }); + + expect(element['_subscribedEntityState']).to.deep.equal(initialState); + }); +}); diff --git a/test/delegates/checks/occupancy.spec.ts b/test/delegates/checks/occupancy.spec.ts index acdbc7ee..1e48e29c 100644 --- a/test/delegates/checks/occupancy.spec.ts +++ b/test/delegates/checks/occupancy.spec.ts @@ -145,6 +145,23 @@ describe('occupancy.ts', () => { expect(result['--occupancy-icon-color']).to.equal('#00ff00'); }); + it('should resolve HA color names and ones with spaces to CSS variables', () => { + const config: AlarmConfig = { + entities: ['binary_sensor.motion_1'], + card_border_color: 'deep-purple', + icon_color: 'accent', + }; + const result = getOccupancyCssVars(true, config); + + expect(result['--occupancy-card-border']).to.equal( + '3px solid var(--deep-purple-color)', + ); + expect(result['--occupancy-card-border-color']).to.equal( + 'var(--deep-purple-color)', + ); + expect(result['--occupancy-icon-color']).to.equal('var(--accent-color)'); + }); + it('should disable card border styles when disabled_card_styles option is set', () => { const config: AlarmConfig = { entities: ['binary_sensor.motion_1'], diff --git a/test/delegates/entities/subscribe-trigger.spec.ts b/test/delegates/entities/subscribe-trigger.spec.ts new file mode 100644 index 00000000..6401e6c5 --- /dev/null +++ b/test/delegates/entities/subscribe-trigger.spec.ts @@ -0,0 +1,33 @@ +import type { HomeAssistant } from '@hass/types'; +import { expect } from 'chai'; +import { subscribeEntityState } from '../../../src/delegates/entities/subscribe-trigger'; + +describe('subscribe-trigger.ts', () => { + describe('subscribeEntityState', () => { + it('calls subscribeMessage with state trigger when connection available', async () => { + const unsubscribeReal = () => {}; + const subscribeMessage = ( + callback: (r: unknown) => void, + msg: unknown, + ) => { + expect(msg).to.deep.equal({ + type: 'subscribe_trigger', + trigger: { platform: 'state', entity_id: 'light.test' }, + }); + return Promise.resolve(unsubscribeReal); + }; + const hass = { + connection: { subscribeMessage }, + } as HomeAssistant & { + connection: { subscribeMessage: typeof subscribeMessage }; + }; + const onChange = () => {}; + const unsubscribe = await subscribeEntityState( + hass, + 'light.test', + onChange, + ); + expect(unsubscribe).to.equal(unsubscribeReal); + }); + }); +}); diff --git a/test/delegates/utils/badge-state.spec.ts b/test/delegates/utils/badge-state.spec.ts index d25e2301..8a882a1b 100644 --- a/test/delegates/utils/badge-state.spec.ts +++ b/test/delegates/utils/badge-state.spec.ts @@ -1,19 +1,14 @@ import { getMatchingBadgeState } from '@delegates/utils/badge-state'; import { createStateEntity } from '@test/test-helpers'; import type { BadgeConfig, StateConfig } from '@type/config/entity'; -import type { EntityInformation } from '@type/room'; +import type { EntityState } from '@type/room'; import { expect } from 'chai'; describe('badge-state.ts', () => { const createEntity = ( state: string, attributes: Record = {}, - ): EntityInformation => ({ - config: { - entity_id: 'light.test', - }, - state: createStateEntity('light', 'test', state, attributes), - }); + ): EntityState => createStateEntity('light', 'test', state, attributes); const createBadge = (states?: StateConfig[]): BadgeConfig => ({ states, @@ -28,14 +23,13 @@ describe('badge-state.ts', () => { }); it('should return undefined when entity state is undefined', () => { - const entity: EntityInformation = { - config: { entity_id: 'light.test' }, - state: undefined, - }; const badge = createBadge([ { state: 'on', icon_color: 'yellow', icon: 'mdi:light-on' }, ]); - const result = getMatchingBadgeState(entity, badge); + const result = getMatchingBadgeState( + undefined as unknown as EntityState, + badge, + ); expect(result).to.be.undefined; }); @@ -163,7 +157,12 @@ describe('badge-state.ts', () => { it('should evaluate states in order - eq before ne', () => { const states: StateConfig[] = [ - { state: 'ok', icon_color: 'green', icon: 'mdi:check', operator: 'eq' }, + { + state: 'ok', + icon_color: 'green', + icon: 'mdi:check', + operator: 'eq', + }, { state: 'ok', icon_color: 'red', icon: 'mdi:alert', operator: 'ne' }, ]; const entity = createEntity('ok'); @@ -175,7 +174,12 @@ describe('badge-state.ts', () => { it('should evaluate states in order - ne matches when eq does not', () => { const states: StateConfig[] = [ - { state: 'ok', icon_color: 'green', icon: 'mdi:check', operator: 'eq' }, + { + state: 'ok', + icon_color: 'green', + icon: 'mdi:check', + operator: 'eq', + }, { state: 'ok', icon_color: 'red', icon: 'mdi:alert', operator: 'ne' }, ]; const entity = createEntity('error'); diff --git a/test/delegates/utils/sensor-sifter.spec.ts b/test/delegates/utils/sensor-sifter.spec.ts new file mode 100644 index 00000000..4b4b2cef --- /dev/null +++ b/test/delegates/utils/sensor-sifter.spec.ts @@ -0,0 +1,277 @@ +import { probablyClassSensorUsersMadeThisComplex } from '@delegates/utils/sensor-sifter'; +import type { AreaRegistryEntry } from '@hass/data/area/area_registry'; +import { createStateEntity as e } from '@test/test-helpers'; +import type { Config } from '@type/config'; +import { expect } from 'chai'; + +describe('sensor-sifter.ts', () => { + const defaultConfig: Config = { area: 'living_room' }; + const defaultClasses = ['temperature', 'humidity', 'illuminance']; + + const area: AreaRegistryEntry = { + area_id: 'living_room', + name: 'Living Room', + icon: 'mdi:sofa', + picture: null, + }; + + describe('exclude_default_entities', () => { + it('should return false when exclude_default_entities is enabled', () => { + const config: Config = { + ...defaultConfig, + features: ['exclude_default_entities'], + }; + const state = e('sensor', 'temp', '72', { device_class: 'temperature' }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + config, + area, + defaultClasses, + ), + ).to.be.false; + }); + }); + + describe('domain and device class filtering', () => { + it('should return false for non-sensor domains', () => { + const state = e('binary_sensor', 'motion', 'on', { + device_class: 'motion', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + area, + defaultClasses, + ), + ).to.be.false; + }); + + it('should return false when device_class is missing', () => { + const state = e('sensor', 'custom', '42'); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + area, + defaultClasses, + ), + ).to.be.false; + }); + + it('should return false when device_class is not in sensorClasses', () => { + const state = e('sensor', 'pressure', '1013', { + device_class: 'pressure', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + area, + defaultClasses, + ), + ).to.be.false; + }); + + it('should return true for a sensor with a matching device_class', () => { + const state = e('sensor', 'temp', '72', { + device_class: 'temperature', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + area, + defaultClasses, + ), + ).to.be.true; + }); + + it('should return true for illuminance sensor', () => { + const state = e('sensor', 'lux', '500', { + device_class: 'illuminance', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + area, + defaultClasses, + ), + ).to.be.true; + }); + }); + + describe('area default sensor logic', () => { + const areaWithDefaults: AreaRegistryEntry = { + ...area, + temperature_entity_id: 'sensor.area_temp', + humidity_entity_id: 'sensor.area_humidity', + }; + + it('should return true for the area default temperature sensor', () => { + const state = e('sensor', 'area_temp', '72', { + device_class: 'temperature', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + areaWithDefaults, + defaultClasses, + ), + ).to.be.true; + }); + + it('should return true for the area default humidity sensor', () => { + const state = e('sensor', 'area_humidity', '45', { + device_class: 'humidity', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + areaWithDefaults, + defaultClasses, + ), + ).to.be.true; + }); + + it('should return false for a non-default temperature sensor when area has a default', () => { + const state = e('sensor', 'other_temp', '68', { + device_class: 'temperature', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + areaWithDefaults, + defaultClasses, + ), + ).to.be.false; + }); + + it('should return false for a non-default humidity sensor when area has a default', () => { + const state = e('sensor', 'other_humidity', '50', { + device_class: 'humidity', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + areaWithDefaults, + defaultClasses, + ), + ).to.be.false; + }); + + it('should still return true for illuminance when area has temp/humidity defaults', () => { + const state = e('sensor', 'lux', '500', { + device_class: 'illuminance', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + areaWithDefaults, + defaultClasses, + ), + ).to.be.true; + }); + }); + + describe('partial area defaults', () => { + it('should allow non-default temp sensors when area only has humidity default', () => { + const areaHumidityOnly: AreaRegistryEntry = { + ...area, + humidity_entity_id: 'sensor.area_humidity', + }; + const state = e('sensor', 'any_temp', '72', { + device_class: 'temperature', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + areaHumidityOnly, + defaultClasses, + ), + ).to.be.true; + }); + + it('should allow non-default humidity sensors when area only has temp default', () => { + const areaTempOnly: AreaRegistryEntry = { + ...area, + temperature_entity_id: 'sensor.area_temp', + }; + const state = e('sensor', 'any_humidity', '45', { + device_class: 'humidity', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + areaTempOnly, + defaultClasses, + ), + ).to.be.true; + }); + }); + + describe('undefined area', () => { + it('should return true for matching sensors when area is undefined', () => { + const state = e('sensor', 'temp', '72', { + device_class: 'temperature', + }); + + expect( + probablyClassSensorUsersMadeThisComplex( + state, + defaultConfig, + undefined, + defaultClasses, + ), + ).to.be.true; + }); + }); + + describe('custom sensor classes', () => { + it('should respect a custom sensorClasses list', () => { + const state = e('sensor', 'pressure', '1013', { + device_class: 'pressure', + }); + + expect( + probablyClassSensorUsersMadeThisComplex(state, defaultConfig, area, [ + 'pressure', + ]), + ).to.be.true; + }); + + it('should reject temperature when not in custom sensorClasses', () => { + const state = e('sensor', 'temp', '72', { + device_class: 'temperature', + }); + + expect( + probablyClassSensorUsersMadeThisComplex(state, defaultConfig, area, [ + 'pressure', + ]), + ).to.be.false; + }); + }); +}); diff --git a/test/html/badge-squad.spec.ts b/test/html/badge-squad.spec.ts index da24cfb1..fa3ff531 100644 --- a/test/html/badge-squad.spec.ts +++ b/test/html/badge-squad.spec.ts @@ -1,7 +1,6 @@ import type { HomeAssistant } from '@hass/types'; import { renderBadgeElements } from '@html/badge-squad'; import { createStateEntity } from '@test/test-helpers'; -import type { BadgeConfig } from '@type/config/entity'; import type { EntityInformation } from '@type/room'; import { expect } from 'chai'; @@ -26,32 +25,20 @@ describe('badge-squad.ts', () => { }); describe('renderBadgeElements', () => { - it('should return empty array when badges is undefined', () => { - const result = renderBadgeElements(undefined, mockEntity, mockHass); - expect(result).to.be.an('array'); - expect(result).to.have.length(0); + it('should return undefined when entity.config.badges is undefined', () => { + const result = renderBadgeElements(mockEntity, mockHass); + expect(result).to.be.undefined; }); - it('should return empty array when badges is empty', () => { - const result = renderBadgeElements([], mockEntity, mockHass); + it('should return empty array when entity.config.badges is empty', () => { + mockEntity.config.badges = []; + const result = renderBadgeElements(mockEntity, mockHass); expect(result).to.be.an('array'); expect(result).to.have.length(0); }); - it('should render single badge', () => { - const badges: BadgeConfig[] = [ - { - position: 'top_right', - mode: 'show_always', - }, - ]; - const result = renderBadgeElements(badges, mockEntity, mockHass); - expect(result).to.have.length(1); - expect(result[0]).to.be.instanceOf(Object); - }); - it('should limit badges to maximum of 4', () => { - const badges: BadgeConfig[] = [ + mockEntity.config.badges = [ { position: 'top_right' }, { position: 'top_left' }, { position: 'bottom_right' }, @@ -59,34 +46,37 @@ describe('badge-squad.ts', () => { { position: 'top_right' }, // 5th badge should be ignored { position: 'top_left' }, // 6th badge should be ignored ]; - const result = renderBadgeElements(badges, mockEntity, mockHass); + const result = renderBadgeElements(mockEntity, mockHass); expect(result).to.have.length(4); }); - it('should render all badges when count is exactly 4', () => { - const badges: BadgeConfig[] = [ - { position: 'top_right' }, - { position: 'top_left' }, - { position: 'bottom_right' }, - { position: 'bottom_left' }, + it('should fall back to entity.config.entity_id when badge has no entity_id', () => { + mockEntity.config.badges = [ + { + position: 'top_right', + mode: 'show_always', + }, ]; - const result = renderBadgeElements(badges, mockEntity, mockHass); - expect(result).to.have.length(4); + const result = renderBadgeElements(mockEntity, mockHass); + expect(result).to.have.length(1); + const badgeConfig = result?.[0]?.values?.[1] as { entity_id?: string }; + expect(badgeConfig?.entity_id).to.equal('light.test'); }); - it('should render badges with correct properties', () => { - const badges: BadgeConfig[] = [ + it('should render badges with correct template structure', () => { + mockEntity.config.badges = [ { position: 'top_right', mode: 'show_always', entity_id: 'sensor.test', }, ]; - const result = renderBadgeElements(badges, mockEntity, mockHass); + const result = renderBadgeElements(mockEntity, mockHass); expect(result).to.have.length(1); - // Verify it's a template result (has strings and values) - expect(result[0]).to.have.property('strings'); - expect(result[0]).to.have.property('values'); + expect(result![0]).to.have.property('strings'); + expect(result![0]).to.have.property('values'); + const badgeConfig = result?.[0]?.values?.[1] as { entity_id?: string }; + expect(badgeConfig?.entity_id).to.equal('sensor.test'); }); }); }); diff --git a/test/index.spec.ts b/test/index.spec.ts index 821f1d73..08bc7ddd 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -92,7 +92,8 @@ describe('index.ts', () => { name: 'Existing Card', description: 'Existing Card Description', preview: true, - documentationURL: 'https://github.com/homeassistant-extras/room-summary-card', + documentationURL: + 'https://github.com/homeassistant-extras/room-summary-card', }, ]; @@ -104,7 +105,8 @@ describe('index.ts', () => { name: 'Existing Card', description: 'Existing Card Description', preview: true, - documentationURL: 'https://github.com/homeassistant-extras/room-summary-card', + documentationURL: + 'https://github.com/homeassistant-extras/room-summary-card', }); }); diff --git a/test/theme/threshold-color.spec.ts b/test/theme/threshold-color.spec.ts index 63cd8925..40ad0f0c 100644 --- a/test/theme/threshold-color.spec.ts +++ b/test/theme/threshold-color.spec.ts @@ -801,7 +801,6 @@ describe('threshold-color.ts', () => { }); }); - describe('getEntityLabel', () => { const createEntity = ( state: string, diff --git a/test/util/comparison-utils.spec.ts b/test/util/comparison-utils.spec.ts index f5aeea60..96815823 100644 --- a/test/util/comparison-utils.spec.ts +++ b/test/util/comparison-utils.spec.ts @@ -1,5 +1,5 @@ -import { meetsStateCondition, meetsThreshold } from '@util/comparison-utils'; import type { ThresholdConfig } from '@type/config/entity'; +import { meetsStateCondition, meetsThreshold } from '@util/comparison-utils'; import { expect } from 'chai'; describe('comparison-utils.ts', () => { diff --git a/test/util/debug.spec.ts b/test/util/debug.spec.ts index 4b33e758..f92dab78 100644 --- a/test/util/debug.spec.ts +++ b/test/util/debug.spec.ts @@ -1,40 +1,92 @@ -import * as featureModule from '@config/feature'; import type { Config } from '@type/config'; import { expect } from 'chai'; import { stub } from 'sinon'; import { d } from '../../src/util/debug'; describe('debug.ts', () => { - let hasFeatureStub: sinon.SinonStub; + it('should log when config.debug exists', () => { + const config: Config = { area: '', debug: {} } as Config; + const consoleDebugStub = stub(console, 'debug'); + + d(config, 'room-summary-card', 'render', 'extra'); + + expect(consoleDebugStub.calledOnce).to.be.true; + expect(consoleDebugStub.calledWith('[room-summary-card] render', 'extra')) + .to.be.true; + consoleDebugStub.restore(); + }); + + it('should not log when config.debug is absent', () => { + const config: Config = { area: '' } as Config; + const consoleDebugStub = stub(console, 'debug'); + + d(config, 'room-summary-card', 'render'); + + expect(consoleDebugStub.notCalled).to.be.true; + consoleDebugStub.restore(); + }); + + it('should not log when config is undefined', () => { + const consoleDebugStub = stub(console, 'debug'); - beforeEach(() => { - hasFeatureStub = stub(featureModule, 'hasFeature'); + d(undefined, 'room-summary-card', 'render'); + + expect(consoleDebugStub.notCalled).to.be.true; + consoleDebugStub.restore(); }); - afterEach(() => { - hasFeatureStub.restore(); + it('should filter by scope when scope is set', () => { + const config: Config = { + area: '', + debug: { scope: ['room-summary-card'] }, + } as Config; + const consoleDebugStub = stub(console, 'debug'); + + d(config, 'room-summary-card', 'render'); + expect(consoleDebugStub.calledOnce).to.be.true; + + consoleDebugStub.resetHistory(); + d(config, 'entity-collection', 'render'); + expect(consoleDebugStub.notCalled).to.be.true; + + consoleDebugStub.restore(); }); - it('should log debug messages when the "debug" feature is enabled', () => { - const config: Config = {} as Config; - hasFeatureStub.withArgs(config, 'debug').returns(true); + it('should filter by categories when categories is set', () => { + const config: Config = { + area: '', + debug: { categories: ['render'] }, + } as Config; const consoleDebugStub = stub(console, 'debug'); - d(config, 'Test message', 123); + d(config, 'room-summary-card', 'render'); + expect(consoleDebugStub.calledOnce).to.be.true; + + consoleDebugStub.resetHistory(); + d(config, 'room-summary-card', 'set hass'); + expect(consoleDebugStub.notCalled).to.be.true; - expect(consoleDebugStub.calledOnceWithExactly('Test message', 123)).to.be - .true; consoleDebugStub.restore(); }); - it('should not log debug messages when the "debug" feature is disabled', () => { - const config: Config = {} as Config; - hasFeatureStub.withArgs(config, 'debug').returns(false); + it('should require both scope and category when both are set', () => { + const config: Config = { + area: '', + debug: { scope: ['room-summary-card'], categories: ['render'] }, + } as Config; const consoleDebugStub = stub(console, 'debug'); - d(config, 'Test message', 123); + d(config, 'room-summary-card', 'render'); + expect(consoleDebugStub.calledOnce).to.be.true; + consoleDebugStub.resetHistory(); + d(config, 'entity-collection', 'render'); expect(consoleDebugStub.notCalled).to.be.true; + + consoleDebugStub.resetHistory(); + d(config, 'room-summary-card', 'set hass'); + expect(consoleDebugStub.notCalled).to.be.true; + consoleDebugStub.restore(); }); }); diff --git a/tsconfig.json b/tsconfig.json index 6003760b..547681c5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "noEmit": true, /* Base Options: */ "skipLibCheck": true, "target": "es2015", diff --git a/yarn.lock b/yarn.lock index 6d682d55..6a2b3243 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@acemir/cssom@^0.9.28": - version "0.9.29" - resolved "https://registry.yarnpkg.com/@acemir/cssom/-/cssom-0.9.29.tgz#3ae97903c49b388a5ff5d231a7a6d42a32e5b148" - integrity sha512-G90x0VW+9nW4dFajtjCoT+NM0scAfH9Mb08IcjgFHYbfiL/lU04dTF9JuVOi3/OH+DJCQdcIseSXkdCB9Ky6JA== +"@acemir/cssom@^0.9.31": + version "0.9.31" + resolved "https://registry.yarnpkg.com/@acemir/cssom/-/cssom-0.9.31.tgz#bd5337d290fb8be2ac18391f37386bc53778b0bc" + integrity sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA== "@ampproject/remapping@^2.2.0": version "2.3.0" @@ -15,27 +15,27 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@asamuzakjp/css-color@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@asamuzakjp/css-color/-/css-color-4.1.0.tgz#4c8c6f48ed2e5c1ad9cc1aa23c80d665e56dd458" - integrity sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w== +"@asamuzakjp/css-color@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@asamuzakjp/css-color/-/css-color-4.1.2.tgz#1123e18b3652ff88fbdd90d61f2e2e44dd8ba28f" + integrity sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg== dependencies: - "@csstools/css-calc" "^2.1.4" - "@csstools/css-color-parser" "^3.1.0" - "@csstools/css-parser-algorithms" "^3.0.5" - "@csstools/css-tokenizer" "^3.0.4" - lru-cache "^11.2.2" + "@csstools/css-calc" "^3.0.0" + "@csstools/css-color-parser" "^4.0.1" + "@csstools/css-parser-algorithms" "^4.0.0" + "@csstools/css-tokenizer" "^4.0.0" + lru-cache "^11.2.5" -"@asamuzakjp/dom-selector@^6.7.6": - version "6.7.6" - resolved "https://registry.yarnpkg.com/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz#9cd7671a61a9cb490852ed6a441b3b0950aab945" - integrity sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg== +"@asamuzakjp/dom-selector@^6.8.1": + version "6.8.1" + resolved "https://registry.yarnpkg.com/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz#39b20993672b106f7cd9a3a9a465212e87e0bfd1" + integrity sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ== dependencies: "@asamuzakjp/nwsapi" "^2.3.9" bidi-js "^1.0.3" css-tree "^3.1.0" is-potential-custom-element-name "^1.0.1" - lru-cache "^11.2.4" + lru-cache "^11.2.6" "@asamuzakjp/nwsapi@^2.3.9": version "2.3.9" @@ -186,6 +186,13 @@ "@babel/helper-string-parser" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1" +"@bramus/specificity@^2.4.2": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@bramus/specificity/-/specificity-2.4.2.tgz#aa8db8eb173fdee7324f82284833106adeecc648" + integrity sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw== + dependencies: + css-tree "^3.0.0" + "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" @@ -193,38 +200,38 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@csstools/color-helpers@^5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz#106c54c808cabfd1ab4c602d8505ee584c2996ef" - integrity sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA== +"@csstools/color-helpers@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@csstools/color-helpers/-/color-helpers-6.0.1.tgz#637c08a61bea78be9b602216f47b0fb93c996178" + integrity sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ== -"@csstools/css-calc@^2.1.4": - version "2.1.4" - resolved "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz" - integrity sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ== +"@csstools/css-calc@^3.0.0": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@csstools/css-calc/-/css-calc-3.1.1.tgz#78b494996dac41a02797dcca18ac3b46d25b3fd7" + integrity sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ== -"@csstools/css-color-parser@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz#4e386af3a99dd36c46fef013cfe4c1c341eed6f0" - integrity sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA== +"@csstools/css-color-parser@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz#c40eac0ad218afb20b91735350270df8454ec307" + integrity sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw== dependencies: - "@csstools/color-helpers" "^5.1.0" - "@csstools/css-calc" "^2.1.4" + "@csstools/color-helpers" "^6.0.1" + "@csstools/css-calc" "^3.0.0" -"@csstools/css-parser-algorithms@^3.0.5": - version "3.0.5" - resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz#5755370a9a29abaec5515b43c8b3f2cf9c2e3076" - integrity sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ== +"@csstools/css-parser-algorithms@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz#e1c65dc09378b42f26a111fca7f7075fc2c26164" + integrity sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w== -"@csstools/css-syntax-patches-for-csstree@1.0.14": - version "1.0.14" - resolved "https://registry.yarnpkg.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.14.tgz#96e8bd829dea29da6460ac7568ee922f48ecc382" - integrity sha512-zSlIxa20WvMojjpCSy8WrNpcZ61RqfTfX3XTaOeVlGJrt/8HF3YbzgFZa01yTbT4GWQLwfTcC3EB8i3XnB647Q== +"@csstools/css-syntax-patches-for-csstree@^1.0.26": + version "1.0.27" + resolved "https://registry.yarnpkg.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz#47caab710c26b5cfba200c5820b11dba29a2fcc9" + integrity sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow== -"@csstools/css-tokenizer@^3.0.4": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz#333fedabc3fd1a8e5d0100013731cf19e6a8c5d3" - integrity sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw== +"@csstools/css-tokenizer@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz#798a33950d11226a0ebb6acafa60f5594424967f" + integrity sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA== "@esm-bundle/chai@^4.3.4-fix.0": version "4.3.4-fix.0" @@ -233,10 +240,10 @@ dependencies: "@types/chai" "^4.2.12" -"@exodus/bytes@^1.6.0": - version "1.7.0" - resolved "https://registry.yarnpkg.com/@exodus/bytes/-/bytes-1.7.0.tgz#0bfe8baefb3843eb28b15eff7fd18edaefff6228" - integrity sha512-5i+BtvujK/vM07YCGDyz4C4AyDzLmhxHMtM5HpUyPRtJPBdFPsj290ffXW+UXY21/G7GtXeHD2nRmq0T1ShyQQ== +"@exodus/bytes@^1.11.0", "@exodus/bytes@^1.6.0": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@exodus/bytes/-/bytes-1.14.1.tgz#9b5c29077162a35f1bd25613e0cd3c239f6e7ad8" + integrity sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ== "@hapi/bourne@^3.0.0": version "3.0.0" @@ -475,101 +482,101 @@ "@types/sinon-chai" "^3.2.3" chai-a11y-axe "^1.5.0" -"@parcel/bundler-default@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.16.3.tgz#1f868c6172bf874ad558c69321c5c49d65d62810" - integrity sha512-zCW2KzMfcEXqpVSU+MbLFMV3mHIzm/7UK1kT8mceuj4UwUScw7Lmjmulc2Ev4hcnwnaAFyaVkyFE5JXA4GKsLQ== +"@parcel/bundler-default@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.16.4.tgz#e4a273ef224b974eab3981a94873f760ef35184b" + integrity sha512-Nb8peNvhfm1+660CLwssWh4weY+Mv6vEGS6GPKqzJmTMw50udi0eS1YuWFzvmhSiu1KsYcUD37mqQ1LuIDtWoA== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/graph" "3.6.3" - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/graph" "3.6.4" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/utils" "2.16.4" nullthrows "^1.1.1" -"@parcel/cache@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.16.3.tgz#dd65df6a10501478cf44621df08136b41de51098" - integrity sha512-iWlbdTk9h7yTG1fxpGvftUD7rVbXVQn1+U21BGqFyYxfrd+wgdN624daIG6+eqI6yBuaBTEwH+cb3kaI9sH1ng== +"@parcel/cache@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.16.4.tgz#54c4d43bad48422acf97cfb7870a780e1c819b7f" + integrity sha512-+uCyeElSga2MBbmbXpIj/WVKH7TByCrKaxtHbelfKKIJpYMgEHVjO4cuc7GUfTrUAmRUS8ZGvnX7Etgq6/jQhw== dependencies: - "@parcel/fs" "2.16.3" - "@parcel/logger" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/fs" "2.16.4" + "@parcel/logger" "2.16.4" + "@parcel/utils" "2.16.4" lmdb "2.8.5" -"@parcel/codeframe@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.16.3.tgz#28c580086b154356d3d1e9fe70092ed283df0a0b" - integrity sha512-oXZx8PUqExnXnAHCLhxulTDeFvTBqPAwJU4AVZwnYFToaQ6nltXWWYaDGUu2f/V3Z17LObWiOROHT7HYXAe62Q== +"@parcel/codeframe@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.16.4.tgz#7698a6c887588daf75995b75d532ac67d57e7249" + integrity sha512-s64aMfOJoPrXhKH+Y98ahX0O8aXWvTR+uNlOaX4yFkpr4FFDnviLcGngDe/Yo4Qq2FJZ0P6dNswbJTUH9EGxkQ== dependencies: chalk "^4.1.2" -"@parcel/compressor-raw@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.16.3.tgz#2f6c426552398136fdfc5e6abf154a652f49a169" - integrity sha512-84lI0ULxvjnqDn3yHorMHj2X2g0oQsIwNFYopQWz9UWjnF7g5IU0EFgAAqMCQxKKUV6fttqaQiDDPikXLR6hHA== - dependencies: - "@parcel/plugin" "2.16.3" - -"@parcel/config-default@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.16.3.tgz#f64d30a6bed04d4a2c8be634a12a9a032ae37cd0" - integrity sha512-OgB6f+EpCzjeFLoVB5qJzKy0ybB2wPK0hB2aXgD3oYCHWLny7LJOGaktY9OskSn1jfz7Tdit9zLNXOhBTMRujw== - dependencies: - "@parcel/bundler-default" "2.16.3" - "@parcel/compressor-raw" "2.16.3" - "@parcel/namer-default" "2.16.3" - "@parcel/optimizer-css" "2.16.3" - "@parcel/optimizer-html" "2.16.3" - "@parcel/optimizer-image" "2.16.3" - "@parcel/optimizer-svg" "2.16.3" - "@parcel/optimizer-swc" "2.16.3" - "@parcel/packager-css" "2.16.3" - "@parcel/packager-html" "2.16.3" - "@parcel/packager-js" "2.16.3" - "@parcel/packager-raw" "2.16.3" - "@parcel/packager-svg" "2.16.3" - "@parcel/packager-wasm" "2.16.3" - "@parcel/reporter-dev-server" "2.16.3" - "@parcel/resolver-default" "2.16.3" - "@parcel/runtime-browser-hmr" "2.16.3" - "@parcel/runtime-js" "2.16.3" - "@parcel/runtime-rsc" "2.16.3" - "@parcel/runtime-service-worker" "2.16.3" - "@parcel/transformer-babel" "2.16.3" - "@parcel/transformer-css" "2.16.3" - "@parcel/transformer-html" "2.16.3" - "@parcel/transformer-image" "2.16.3" - "@parcel/transformer-js" "2.16.3" - "@parcel/transformer-json" "2.16.3" - "@parcel/transformer-node" "2.16.3" - "@parcel/transformer-postcss" "2.16.3" - "@parcel/transformer-posthtml" "2.16.3" - "@parcel/transformer-raw" "2.16.3" - "@parcel/transformer-react-refresh-wrap" "2.16.3" - "@parcel/transformer-svg" "2.16.3" - -"@parcel/core@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.16.3.tgz#fb805957022880cd014f6815e58b315cacd4bd74" - integrity sha512-b9ll4jaFYfXSv6NZAOJ2P0uuyT/Doel7ho2AHLSUz2thtcL6HEb2+qdV2f9wriVvbEoPAj9VuSOgNc0t0f5iMw== +"@parcel/compressor-raw@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.16.4.tgz#93ef4db8710ab7bc0efd88f827bca41f6b45d3cc" + integrity sha512-IK8IpNhw61B2HKgA1JhGhO9y+ZJFRZNTEmvhN1NdLdPqvgEXm2EunT+m6D9z7xeoeT6XnUKqM0eRckEdD0OXbA== + dependencies: + "@parcel/plugin" "2.16.4" + +"@parcel/config-default@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.16.4.tgz#5e695f26d6d8ef94b33904edc000cbeb257f63e5" + integrity sha512-kBxuTY/5trEVnvXk92l7LVkYjNuz3SaqWymFhPjEnc8GY4ZVdcWrWdXWTB9hUhpmRYJctFCyGvM0nN05JTiM2g== + dependencies: + "@parcel/bundler-default" "2.16.4" + "@parcel/compressor-raw" "2.16.4" + "@parcel/namer-default" "2.16.4" + "@parcel/optimizer-css" "2.16.4" + "@parcel/optimizer-html" "2.16.4" + "@parcel/optimizer-image" "2.16.4" + "@parcel/optimizer-svg" "2.16.4" + "@parcel/optimizer-swc" "2.16.4" + "@parcel/packager-css" "2.16.4" + "@parcel/packager-html" "2.16.4" + "@parcel/packager-js" "2.16.4" + "@parcel/packager-raw" "2.16.4" + "@parcel/packager-svg" "2.16.4" + "@parcel/packager-wasm" "2.16.4" + "@parcel/reporter-dev-server" "2.16.4" + "@parcel/resolver-default" "2.16.4" + "@parcel/runtime-browser-hmr" "2.16.4" + "@parcel/runtime-js" "2.16.4" + "@parcel/runtime-rsc" "2.16.4" + "@parcel/runtime-service-worker" "2.16.4" + "@parcel/transformer-babel" "2.16.4" + "@parcel/transformer-css" "2.16.4" + "@parcel/transformer-html" "2.16.4" + "@parcel/transformer-image" "2.16.4" + "@parcel/transformer-js" "2.16.4" + "@parcel/transformer-json" "2.16.4" + "@parcel/transformer-node" "2.16.4" + "@parcel/transformer-postcss" "2.16.4" + "@parcel/transformer-posthtml" "2.16.4" + "@parcel/transformer-raw" "2.16.4" + "@parcel/transformer-react-refresh-wrap" "2.16.4" + "@parcel/transformer-svg" "2.16.4" + +"@parcel/core@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.16.4.tgz#a0252f118e3895ae6591f41c4d4c0c6015fbb151" + integrity sha512-a0CgrW5A5kwuSu5J1RFRoMQaMs9yagvfH2jJMYVw56+/7NRI4KOtu612SG9Y1ERWfY55ZwzyFxtLWvD6LO+Anw== dependencies: "@mischnic/json-sourcemap" "^0.1.1" - "@parcel/cache" "2.16.3" - "@parcel/diagnostic" "2.16.3" - "@parcel/events" "2.16.3" - "@parcel/feature-flags" "2.16.3" - "@parcel/fs" "2.16.3" - "@parcel/graph" "3.6.3" - "@parcel/logger" "2.16.3" - "@parcel/package-manager" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/profiler" "2.16.3" - "@parcel/rust" "2.16.3" + "@parcel/cache" "2.16.4" + "@parcel/diagnostic" "2.16.4" + "@parcel/events" "2.16.4" + "@parcel/feature-flags" "2.16.4" + "@parcel/fs" "2.16.4" + "@parcel/graph" "3.6.4" + "@parcel/logger" "2.16.4" + "@parcel/package-manager" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/profiler" "2.16.4" + "@parcel/rust" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/types" "2.16.3" - "@parcel/utils" "2.16.3" - "@parcel/workers" "2.16.3" + "@parcel/types" "2.16.4" + "@parcel/utils" "2.16.4" + "@parcel/workers" "2.16.4" base-x "^3.0.11" browserslist "^4.24.5" clone "^2.1.2" @@ -580,361 +587,361 @@ nullthrows "^1.1.1" semver "^7.7.1" -"@parcel/diagnostic@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.16.3.tgz#e402872fbf517b381518b2add857b940a3af87db" - integrity sha512-NBoGGFMqOmbs8i0zGVwTeU0alQ0BkEZe894zAb5jEBQqsRBPmdqogwmARsT4Ix2bN1QBco4o0gn9kBtalFC6IQ== +"@parcel/diagnostic@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.16.4.tgz#f9169eb08b42c806a69172d3d233a01b801994f1" + integrity sha512-YN5CfX7lFd6yRLxyZT4Sj3sR6t7nnve4TdXSIqapXzQwL7Bw+sj79D95wTq2rCm3mzk5SofGxFAXul2/nG6gcQ== dependencies: "@mischnic/json-sourcemap" "^0.1.1" nullthrows "^1.1.1" -"@parcel/error-overlay@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/error-overlay/-/error-overlay-2.16.3.tgz#89e726c7a955b37441b00e0ff8bca3244dfd7c17" - integrity sha512-JqJR4Fl5SwTmqDEuCAC8F1LmNLWpjfiJ+hGp3CoLb0/9EElRxlpkuP/SxTe2/hyXevpfn3bfvS1cn/mWhHUc3w== - -"@parcel/events@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.16.3.tgz#69d6e54410b8de0d28c00f31c6527af4dab82db8" - integrity sha512-rAh/yXwtHYcKWmi9Tjjf5t95UdBVhhlyJkIYN25/PYKdSRBcQ9c1rd8/fvOeZKy1/fSiOcEXqm6dK7bhLSCaww== - -"@parcel/feature-flags@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/feature-flags/-/feature-flags-2.16.3.tgz#b74f66fcd7b3e63b020bd6b534e77376a150c668" - integrity sha512-D15/cM/mAO8yv0NQ9kFBxXZ7C3A+jAq+9tVfrjYegofMk18pQoXJz6X/po2Kq1PzO7pjydn7PqYMB/O9p/+zbQ== - -"@parcel/fs@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.16.3.tgz#28e618009015eea7fd942da0f16e2ac6caf58ba1" - integrity sha512-InMXHVIfDUSimjBoGJcdNlNjoIsDQ8MUDN8UJG4jnjJQ6DDor+W+yg4sw/40tToUqIyi99lVhQlpkBA+nHLpOQ== - dependencies: - "@parcel/feature-flags" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/types-internal" "2.16.3" - "@parcel/utils" "2.16.3" +"@parcel/error-overlay@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/error-overlay/-/error-overlay-2.16.4.tgz#8edc66f8ff7c2d6c57f9ae0ee2cf49e48ba03756" + integrity sha512-e8KYKnMsfmQnqIhsUWBUZAXlDK30wkxsAGle1tZ0gOdoplaIdVq/WjGPatHLf6igLM76c3tRn2vw8jZFput0jw== + +"@parcel/events@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.16.4.tgz#657f5b0ef305bb533e8c5df3538b4cef36bef0fd" + integrity sha512-slWQkBRAA7o0cN0BLEd+yCckPmlVRVhBZn5Pn6ktm4EzEtrqoMzMeJOxxH8TXaRzrQDYnTcnYIHFgXWd4kkUfg== + +"@parcel/feature-flags@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/feature-flags/-/feature-flags-2.16.4.tgz#daa88354e7d024b995e750e38bd319717cd70a69" + integrity sha512-nYdx53siKPLYikHHxfzgjzzgxdrjquK6DMnuSgOTyIdRG4VHdEN0+NqKijRLuVgiUFo/dtxc2h+amwqFENMw8w== + +"@parcel/fs@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.16.4.tgz#771c4494acd3052c421891e3da755ee3d9109f43" + integrity sha512-maCMOiVn7oJYZlqlfxgLne8n6tSktIT1k0AeyBp4UGWCXyeJUJ+nL7QYShFpKNLtMLeF0cEtgwRAknWzbcDS1g== + dependencies: + "@parcel/feature-flags" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/types-internal" "2.16.4" + "@parcel/utils" "2.16.4" "@parcel/watcher" "^2.0.7" - "@parcel/workers" "2.16.3" + "@parcel/workers" "2.16.4" -"@parcel/graph@3.6.3": - version "3.6.3" - resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-3.6.3.tgz#122a892d32fec190a08a251d2a856fbdb8f6a776" - integrity sha512-3qV99HCHrPR1CnMOHkwwpmPBimVMd3d/GcEcgOHUKi+2mS0KZ4TwMs/THaIWtJx7q5jrhqEht+IyQ1Smupo49g== +"@parcel/graph@3.6.4": + version "3.6.4" + resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-3.6.4.tgz#7f76142f8dae19c4753c3962d9668c3b3769fb3e" + integrity sha512-Cj9yV+/k88kFhE+D+gz0YuNRpvNOCVDskO9pFqkcQhGbsGq6kg2XpZ9V7HlYraih31xf8Vb589bZOwjKIiHixQ== dependencies: - "@parcel/feature-flags" "2.16.3" + "@parcel/feature-flags" "2.16.4" nullthrows "^1.1.1" -"@parcel/logger@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.16.3.tgz#074d18e8714d4d7445f00cd949a6d7b905e9f84f" - integrity sha512-dHUJk8dvo2wOg3dIqSjNGqlVqsRn4hTZVbgTShaImaLTWdueaKfMojxo79P7T3em49y0dQb0m+xl2SunDhtwsA== +"@parcel/logger@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.16.4.tgz#c21d695030f62c3c5be90a811942aff402ff99c3" + integrity sha512-QR8QLlKo7xAy9JBpPDAh0RvluaixqPCeyY7Fvo2K7hrU3r85vBNNi06pHiPbWoDmB4x1+QoFwMaGnJOHR+/fMA== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/events" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/events" "2.16.4" -"@parcel/markdown-ansi@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.16.3.tgz#96db3630b3f2fcad50d8a943217e2c33c51b2454" - integrity sha512-r0QQpS44jNueY8lcZcSoUua3kJfI5kDZrJvFgi1jrkyxwDUfq3L0xWQjxHrXzv8K6uFAeU+teoq8JcWLVLXa1w== +"@parcel/markdown-ansi@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.16.4.tgz#8d11a49327eb38880d4b46ecb560e547ff93f57b" + integrity sha512-0+oQApAVF3wMcQ6d1ZfZ0JsRzaMUYj9e4U+naj6YEsFsFGOPp+pQYKXBf1bobQeeB7cPKPT3SUHxFqced722Hw== dependencies: chalk "^4.1.2" -"@parcel/namer-default@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.16.3.tgz#e67ea39e3e1dc19a3b65694721147db0323f44e1" - integrity sha512-4MwRm8ZnloMdQ6sAMrTDxMiPVN1fV+UcBIrA0Fpp4kD3XLkqSAUCLnjl13+VrPelfh01irM6QnpK4JTKBqRk0A== +"@parcel/namer-default@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.16.4.tgz#3b2d3c80f41071a199d136bb26953fb377517432" + integrity sha512-CE+0lFg881sJq575EXxj2lKUn81tsS5itpNUUErHxit195m3PExyAhoXM6ed/SXxwi+uv+T5FS/jjDLBNuUFDA== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" nullthrows "^1.1.1" -"@parcel/node-resolver-core@3.7.3": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-3.7.3.tgz#6ec9c4ad0a0733fc1e3f4acbabb0e065250fa763" - integrity sha512-0xdXyhGcGwtYmfWwEwzdVVGnTaADdTScx1S8IXiK0Nh3S1b4ilGqnKzw8fVsJCsBMvQA5e251EDFeG3qTnUsnw== +"@parcel/node-resolver-core@3.7.4": + version "3.7.4" + resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-3.7.4.tgz#d7eafcb42b1118621a5d33216c3d77f36ec8ed5d" + integrity sha512-b3VDG+um6IWW5CTod6M9hQsTX5mdIelKmam7mzxzgqg4j5hnycgTWqPMc9UxhYoUY/Q/PHfWepccNcKtvP5JiA== dependencies: "@mischnic/json-sourcemap" "^0.1.1" - "@parcel/diagnostic" "2.16.3" - "@parcel/fs" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/fs" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/utils" "2.16.4" nullthrows "^1.1.1" semver "^7.7.1" -"@parcel/optimizer-css@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-css/-/optimizer-css-2.16.3.tgz#ad6c9d34610b4817de8342c278f629068cafc433" - integrity sha512-j/o9bGtu1Fe7gJYQD+/SeJ5yR7FmS6Z7e6CtTkVxjeeq0/IdR0KoZOCkJ4cRETPnm+wkyQVlY8koAAFbEEqV8w== +"@parcel/optimizer-css@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-css/-/optimizer-css-2.16.4.tgz#269584b0d739781ed5218316541cbc7a1484d2bc" + integrity sha512-aqdXCtmvpcXYgJFGk2DtXF34wuM2TD1fZorKMrJdKB9sSkWVRs1tq6RAXQrbi0ZPDH9wfE/9An3YdkTex7RHuQ== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/utils" "2.16.3" + "@parcel/utils" "2.16.4" browserslist "^4.24.5" lightningcss "^1.30.1" nullthrows "^1.1.1" -"@parcel/optimizer-html@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-html/-/optimizer-html-2.16.3.tgz#04ca8a9e48412920fd6340312b57a2c2a073968e" - integrity sha512-EBmjY+QRa/in05wRWiL6B/kQ1ERemdg4W9py+V2w0tJx1n6yOvtjPGvivYtU+s82rlVlx6DN3DFU13iGRt0FuQ== - dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/utils" "2.16.3" - -"@parcel/optimizer-image@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.16.3.tgz#9b43f33ed8db0c6da254dec27546026b38248785" - integrity sha512-PbGsDXbbWyOnkpWn3jgZxtAp8l8LNXl7DCv5Q4l1TR6k4sULjmxTTPY6+AkY6H84cAN7s5h6F8k2XeN3ygXWCA== - dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/utils" "2.16.3" - "@parcel/workers" "2.16.3" - -"@parcel/optimizer-svg@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-svg/-/optimizer-svg-2.16.3.tgz#0ef8bdfc4466128b4d40927ed6069df05f668181" - integrity sha512-fgQhrqu5pKtEaM9G//PvBZSuCDP6ZVbGyFnePKCzqnXJ173/Y+4kUbNOrPi7wE4HupWMsJRNUf/vyCu+lXdOiQ== - dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/utils" "2.16.3" - -"@parcel/optimizer-swc@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-swc/-/optimizer-swc-2.16.3.tgz#50241771fea269cbcaa4b62bfbc900ab3f91856b" - integrity sha512-8P5Bis2SynQ6sPW1bwB6H8WK+nFF61RCKzlGnTPoh1YE36dubYqUreYYISMLFt/rG8eb+Ja78DQLPZTVP3sfQQ== - dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" +"@parcel/optimizer-html@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-html/-/optimizer-html-2.16.4.tgz#85d8975288c499419a47341975e7aa42c5fb7bf3" + integrity sha512-vg/R2uuSni+NYYUUV8m+5bz8p5zBv8wc/nNleoBnGuCDwn7uaUwTZ8Gt9CjZO8jjG0xCLILoc/TW+e2FF3pfgQ== + dependencies: + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/utils" "2.16.4" + +"@parcel/optimizer-image@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.16.4.tgz#878a3d29af2fde7f9e37dd6f496a3bdacb498dc0" + integrity sha512-2RV54WnvMYr18lxSx7Zlx/DXpJwMzOiPxDnoFyvaUoYutvgHO6chtcgFgh1Bvw/PoI95vYzlTkZ8QfUOk5A0JA== + dependencies: + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/utils" "2.16.4" + "@parcel/workers" "2.16.4" + +"@parcel/optimizer-svg@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-svg/-/optimizer-svg-2.16.4.tgz#f2131c60a4144e7bcfe5f8ac1e100e32a0d54a88" + integrity sha512-22+BqIffCrVErg8y2XwhasbTaFNn75OKXZ3KTDBIfOSAZKLUKs1iHfDXETzTRN7cVcS+Q36/6EHd7N/RA8i1fg== + dependencies: + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/utils" "2.16.4" + +"@parcel/optimizer-swc@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/optimizer-swc/-/optimizer-swc-2.16.4.tgz#29b5b6a7c2245dc8fe24da0136d34a56af09d99c" + integrity sha512-+URqwnB6u1gqaLbG1O1DDApH+UVj4WCbK9No1fdxLBxQ9a84jyli25o1kK1hYB9Nb/JMyYNnEBfvYUW6RphOxw== + dependencies: + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/utils" "2.16.3" + "@parcel/utils" "2.16.4" "@swc/core" "^1.11.24" nullthrows "^1.1.1" -"@parcel/package-manager@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.16.3.tgz#00acb13ef7bd47ff14e1aa3914dd638835ed4eb5" - integrity sha512-TySTY93SyGfu8E5YWiekumw6sm/2+LBHcpv1JWWAfNd+1b/x3WB5QcRyEk6mpnOo7ChQOfqykzUaBcrmLBGaSw== - dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/fs" "2.16.3" - "@parcel/logger" "2.16.3" - "@parcel/node-resolver-core" "3.7.3" - "@parcel/types" "2.16.3" - "@parcel/utils" "2.16.3" - "@parcel/workers" "2.16.3" +"@parcel/package-manager@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.16.4.tgz#186df5779bae20aa563d1c4778ba3a17310d7e58" + integrity sha512-obWv9gZgdnkT3Kd+fBkKjhdNEY7zfOP5gVaox5i4nQstVCaVnDlMv5FwLEXwehL+WbwEcGyEGGxOHHkAFKk7Cg== + dependencies: + "@parcel/diagnostic" "2.16.4" + "@parcel/fs" "2.16.4" + "@parcel/logger" "2.16.4" + "@parcel/node-resolver-core" "3.7.4" + "@parcel/types" "2.16.4" + "@parcel/utils" "2.16.4" + "@parcel/workers" "2.16.4" "@swc/core" "^1.11.24" semver "^7.7.1" -"@parcel/packager-css@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.16.3.tgz#b30411bedf8e7351f3d3d5a7046a30a405767153" - integrity sha512-CUwMRif1ZGBfociDt6m18L7sgafsquo0+NYRDXCTHmig3w7zm5saE4PXborfzRI/Lj3kBUkJYH//NQGITHv1Yg== +"@parcel/packager-css@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.16.4.tgz#cc6ed5c466dd45a2d3a7b9b2163d5886d2d22b00" + integrity sha512-rWRtfiX+VVIOZvq64jpeNUKkvWAbnokfHQsk/js1s5jD4ViNQgPcNLiRaiIANjymqL6+dQqWvGUSW2a5FAZYfg== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/utils" "2.16.3" + "@parcel/utils" "2.16.4" lightningcss "^1.30.1" nullthrows "^1.1.1" -"@parcel/packager-html@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.16.3.tgz#349610c6fd5c586553ddbfda4f854c33c518293b" - integrity sha512-hluJXpvcW2EwmBxO/SalBiX5SIYJ7jGTkhFq5ka2wrQewFxaAOv2BVTuFjl1AAnWzjigcNhC4n0jkQUckCNW4g== +"@parcel/packager-html@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.16.4.tgz#6e785cc4b424503fe13389cecad517c85383c8f6" + integrity sha512-AWo5f6SSqBsg2uWOsX0gPX8hCx2iE6GYLg2Z4/cDy2mPlwDICN8/bxItEztSZFmObi+ti26eetBKRDxAUivyIQ== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/types" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/types" "2.16.4" + "@parcel/utils" "2.16.4" -"@parcel/packager-js@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.16.3.tgz#384c7cf52066f87844ee922d6ff5659f424aac3e" - integrity sha512-01fufzVOs9reEDq9OTUyu5Kpasd8nGvBJEUytagM6rvNlEpmlUX5HvoAzUMSTyYeFSH+1VnX6HzK6EcQNY9Y8Q== +"@parcel/packager-js@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.16.4.tgz#ee1d37ed4322a38b34627015f88510204fef071b" + integrity sha512-L2o39f/fhta+hxto7w8OTUKdstY+te5BmHZREckbQm0KTBg93BG7jB0bfoxLSZF0d8uuAYIVXjzeHNqha+du1g== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/types" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/types" "2.16.4" + "@parcel/utils" "2.16.4" globals "^13.24.0" nullthrows "^1.1.1" -"@parcel/packager-raw@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.16.3.tgz#f8afd492942c60d6a250f620999ecdb66bb63bdf" - integrity sha512-GCehb36D2xe8P8gftyZcjNr3XcUzBgRzWcasM4I0oPaLRZw4nuIu60cwTsGk6/HhUYDq8uPze+gr1L4pApRrjw== +"@parcel/packager-raw@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.16.4.tgz#6390274cc7784e037ccb5ad226777beb588d141e" + integrity sha512-A9j60G9OmbTkEeE4WRMXCiErEprHLs9NkUlC4HXCxmSrPMOVaMaMva2LdejE3A9kujZqYtYfuc8+a+jN+Nro4w== dependencies: - "@parcel/plugin" "2.16.3" + "@parcel/plugin" "2.16.4" -"@parcel/packager-svg@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.16.3.tgz#8974add3addbf0d328722d4638e2cadc3800d7ac" - integrity sha512-1TLmU8zcRBySOD3WXGUhTjmIurJoOMwQ3aIiyHXn4zjrl4+VPw/WnUoVGpMwUW1T7rb2/22BKPGAAxbOLDqxLQ== +"@parcel/packager-svg@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.16.4.tgz#61736d3bd6c43507b9daaa6dcb9501edb6108892" + integrity sha512-LT9l7eInFrAZJ6w3mYzAUgDq3SIzYbbQyW46Dz26M9lJQbf6uCaATUTac3BEHegW0ikDuw4OOGHK41BVqeeusg== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/types" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/types" "2.16.4" + "@parcel/utils" "2.16.4" -"@parcel/packager-wasm@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/packager-wasm/-/packager-wasm-2.16.3.tgz#ef5fdff348659a59d26bee936786b15db39be430" - integrity sha512-RfRM/RaA4eWV+qUt7A9Vo2VlvZx50Rfs81kZ4WBhxzey2BGAvBSJWceYEUnI7JuDmrHjDMDe6y0+gLNmELeL1g== +"@parcel/packager-wasm@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/packager-wasm/-/packager-wasm-2.16.4.tgz#2869d1df61c0247d22b43769024310784a5f5f05" + integrity sha512-AY96Aqu/RpmaSZK2RGkIrZWjAperDw8DAlxLAiaP1D/RPVnikZtl5BmcUt/Wz3PrzG7/q9ZVqqKkWsLmhkjXZQ== dependencies: - "@parcel/plugin" "2.16.3" + "@parcel/plugin" "2.16.4" -"@parcel/plugin@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.16.3.tgz#844bfe6f841ab200c52f46ef6e970e2cdf1122bd" - integrity sha512-w4adN/E2MBbNzUwuGWcUkilrf7B6eQThPRdgiw2awIY0/t0C1gN/hhBfUeWt7vt0WcvWlXcyR/OGzU/r0nPteA== +"@parcel/plugin@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.16.4.tgz#a7fa42863fc85215cb4ede5ef1b709075dd42317" + integrity sha512-aN2VQoRGC1eB41ZCDbPR/Sp0yKOxe31oemzPx1nJzOuebK2Q6FxSrJ9Bjj9j/YCaLzDtPwelsuLOazzVpXJ6qg== dependencies: - "@parcel/types" "2.16.3" + "@parcel/types" "2.16.4" -"@parcel/profiler@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/profiler/-/profiler-2.16.3.tgz#a154c0bb4b29c559381e6a21b10f31cf81863682" - integrity sha512-/4cVsLfv36fdphm+JiReeXXT3RD6258L79C2kjpD06i84sxyNPQVbFldgWRppbHW2KBR/D6XhIzHcwoDUYtTbw== +"@parcel/profiler@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/profiler/-/profiler-2.16.4.tgz#f5e732fa5be2a634a37e35b189652d6d67bd5a11" + integrity sha512-R3JhfcnoReTv2sVFHPR2xKZvs3d3IRrBl9sWmAftbIJFwT4rU70/W7IdwfaJVkD/6PzHq9mcgOh1WKL4KAxPdA== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/events" "2.16.3" - "@parcel/types-internal" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/events" "2.16.4" + "@parcel/types-internal" "2.16.4" chrome-trace-event "^1.0.2" -"@parcel/reporter-cli@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.16.3.tgz#bb95adaeae85d4d54f5d9d06703fa556a5872ca4" - integrity sha512-kIwhJy97xlgvNsUhn3efp6PxUfWCiiPG9ciDnAGBXpFmKWl63WQR6QIXNuNgrQremUTzIHJ02h6/+LyBJD4wjw== +"@parcel/reporter-cli@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.16.4.tgz#0da65678405755cef281f36ac4ddb6b62248804c" + integrity sha512-DQx9TwcTZrDv828+tcwEi//xyW7OHTGzGX1+UEVxPp0mSzuOmDn0zfER8qNIqGr1i4D/FXhb5UJQDhGHV8mOpQ== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/types" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/types" "2.16.4" + "@parcel/utils" "2.16.4" chalk "^4.1.2" term-size "^2.2.1" -"@parcel/reporter-dev-server@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.16.3.tgz#45022541c20adcd524dd6875141492f4eee452b8" - integrity sha512-c2YEHU3ePOSUO+JXoehn3r0ruUlP2i4xvHfwHLHI3NW/Ymlp4Gy9rWyyYve/zStfoEOyMN/vKRWKtxr6nCy9DQ== +"@parcel/reporter-dev-server@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.16.4.tgz#ec32a91e20beb5daa5a5e8f137247563ad70baef" + integrity sha512-YWvay25htQDifpDRJ0+yFh6xUxKnbfeJxYkPYyuXdxpEUhq4T0UWW0PbPCN/wFX7StgeUTXq5Poeo/+eys9m3w== dependencies: - "@parcel/codeframe" "2.16.3" - "@parcel/plugin" "2.16.3" + "@parcel/codeframe" "2.16.4" + "@parcel/plugin" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/utils" "2.16.3" + "@parcel/utils" "2.16.4" -"@parcel/reporter-tracer@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/reporter-tracer/-/reporter-tracer-2.16.3.tgz#e97c5fd83cd6b1113483634e77e8dc7edea14ba6" - integrity sha512-DqQQRQC6JKQcYo8fAC69JGri++WC9cTRZFH2QJdbcMXnmeCW0YjBwHsl65C0Q/8aO6lwVlV0P1waMPW3iQw+uA== +"@parcel/reporter-tracer@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/reporter-tracer/-/reporter-tracer-2.16.4.tgz#34e44123e444e6aa64cdc81d59bf333802deb74c" + integrity sha512-JKnlXpPepak0/ZybmZn9JtyjJiDBWYrt7ZUlXQhQb0xzNcd/k+RqfwVkTKIwyFHsWtym0cwibkvsi2bWFzS7tw== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/utils" "2.16.4" chrome-trace-event "^1.0.3" nullthrows "^1.1.1" -"@parcel/resolver-default@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.16.3.tgz#cbdf25cc229734780251baab6b19b329224923a8" - integrity sha512-2bf2VRKt1fZRZbi85SBLrePr4Eid0zXUQMy+MRcFoVZ8MaxsjvWjnlxHW71cWNcRQATUOX/0w0z0Gcf7Kjrh2g== +"@parcel/resolver-default@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.16.4.tgz#d7b90177e2bee662cca57edb5c0eb82e8034ea2f" + integrity sha512-wJe9XQS0hn/t32pntQpJbls3ZL8mGVVhK9L7s7BTmZT9ufnvP2nif1psJz/nbgnP9LF6mLSk43OdMJKpoStsjQ== dependencies: - "@parcel/node-resolver-core" "3.7.3" - "@parcel/plugin" "2.16.3" + "@parcel/node-resolver-core" "3.7.4" + "@parcel/plugin" "2.16.4" -"@parcel/runtime-browser-hmr@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.16.3.tgz#db9474c253a3ecee48d611470547260320686479" - integrity sha512-dN5Kv6/BLaKAf80zogimvSPZYQRA+h+o3rKQLnxid2FilVRTCjz+FOcuMsT/EqAJXai1mKjrxtqlM9IJ4oSV1A== +"@parcel/runtime-browser-hmr@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.16.4.tgz#519b86a6fde8c3f5ef3ef5b714fa208a029f7059" + integrity sha512-asx7p3NjUSfibI3bC7+8+jUIGHWVk2Zuq9SjJGCGDt+auT9A4uSGljnsk1BWWPqqZ0WILubq4czSAqm0+wt4cw== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/utils" "2.16.4" -"@parcel/runtime-js@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.16.3.tgz#d63b83214af5e36099863a3bf8dc06ed1c235097" - integrity sha512-Xk1G7A0g5Dbm374V8piDbxLRQoQ1JiKIChXzQuiQ755A22JYOSP0yA2djBEuB7KWPwFKDd4f9DFTVDn6VclPaQ== +"@parcel/runtime-js@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.16.4.tgz#ea7c7f1c8b0997c6a6b33561b65d8e7ed305d65c" + integrity sha512-gUKmsjg+PULQBu2QbX0QKll9tXSqHPO8NrfxHwWb2lz5xDKDos1oV0I7BoMWbHhUHkoToXZrm654oGViujtVUA== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/utils" "2.16.4" nullthrows "^1.1.1" -"@parcel/runtime-rsc@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/runtime-rsc/-/runtime-rsc-2.16.3.tgz#1cc804e83473b3fecb0e528571283ae53925a0ec" - integrity sha512-QR+4BjGE2OqLcjh6WfAMrNoM0FubxvJNH9p31yjI4H1ivrvTJECanvVZ6C7QRR/30l+WAYb5USrcYJVMwHi1zg== +"@parcel/runtime-rsc@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/runtime-rsc/-/runtime-rsc-2.16.4.tgz#e57c229bfcc3464772dc5ea893118a3a5edb41cf" + integrity sha512-CHkotYE/cNiUjJmrc5FD9YhlFp1UF5wMNNJmoWaL40eBzsqcaV0sSn5V3bNapwewn3wrMYgdPgvOTHfaZaG73A== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/utils" "2.16.4" nullthrows "^1.1.1" -"@parcel/runtime-service-worker@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.16.3.tgz#70dc723e2549bcf7985b6325fa1c19f419979e49" - integrity sha512-O+jhRFNThRAxsHOW6RYcYR6+sA9MxeGTmbVRguFyM12OqzuXRTuuv9x2RDSGP/cgBBCpVuq5JvK8KwS2RB26Gg== +"@parcel/runtime-service-worker@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.16.4.tgz#f0200cb88f0b77d2ecbdf14b9b27a43572811010" + integrity sha512-FT0Q58bf5Re+dq5cL2XHbxqHHFZco6qtRijeVpT3TSPMRPlniMArypSytTeZzVNL7h/hxjWsNu7fRuC0yLB5hA== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/utils" "2.16.4" nullthrows "^1.1.1" -"@parcel/rust-darwin-arm64@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-darwin-arm64/-/rust-darwin-arm64-2.16.3.tgz#2531454733a705aaf55099f08cc308f416277dd0" - integrity sha512-9JG19DDNjIpvlI1b8VYIjvCaulftd6/J09/Rj2A8KgREv6EtCDkus8jCsNw7Jacj2HIWg23kxJY3XKcJ9pkiug== - -"@parcel/rust-darwin-x64@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-darwin-x64/-/rust-darwin-x64-2.16.3.tgz#9bff6f0f7070a7405fc2cc0d3740df60eda7fbd1" - integrity sha512-9mG6M6SGYiCO9IfD85Bixg5udXoy2IQHCRdBoQmpNej5+FrDW1a3FeDwDzqOFtl9b7axpzPEVb7zp+WK36Rn4w== - -"@parcel/rust-linux-arm-gnueabihf@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-linux-arm-gnueabihf/-/rust-linux-arm-gnueabihf-2.16.3.tgz#951f2ab7e55af545ed08aadc62968ddeeee90ddd" - integrity sha512-zSA1Dz5JWS28DkEMjEQNmf8qk55dR6rcKtwrw5CMg3Ndt30ugrGtRechsqEpXSYYxcDY1kmZ779LwiTUdkdCrQ== - -"@parcel/rust-linux-arm64-gnu@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-linux-arm64-gnu/-/rust-linux-arm64-gnu-2.16.3.tgz#37588b03300600e4af191631b622762b4a29dcac" - integrity sha512-PvjO0U6qM0JjRCH2eKi3JNKgBVWDBP3VrMEUXJJM8K37ylfLTozK0f7oK2M03voCS1WjKrduRGjJNk8EZrBPow== - -"@parcel/rust-linux-arm64-musl@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-linux-arm64-musl/-/rust-linux-arm64-musl-2.16.3.tgz#d724b65ee432ffec21e8e712b4cec4b43ec6055f" - integrity sha512-a4TZB9/Y/y8DQ55XZXh9bNb5yIC9CAoK2YK8g3OytauC8OrHGtIIVlF+E1UCn/FPBFr2dobYOeih/InvLKITpQ== - -"@parcel/rust-linux-x64-gnu@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-linux-x64-gnu/-/rust-linux-x64-gnu-2.16.3.tgz#bca956ad2ec11eb06a55516dad2c954df9de8bfb" - integrity sha512-6/a/5jDcVwE0xpLSLGI9T2pclgnad0jVFRH/4Gm9yQ5fl2gpYghjg3fcCNeSjJ/aBNFKlOeKLlp/oBSlTtlkoQ== - -"@parcel/rust-linux-x64-musl@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-linux-x64-musl/-/rust-linux-x64-musl-2.16.3.tgz#faea88a0c31febcd6dad59e9cc7000edc1ac77b9" - integrity sha512-gTUlFvJBLR3UxNjGs076wVuFZyx+X6G6opJzBFaSG9XqLhLo+VrpqHpjCx+SCwSufDLTVq8rWJbwpvbe2EhRJg== - -"@parcel/rust-win32-x64-msvc@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust-win32-x64-msvc/-/rust-win32-x64-msvc-2.16.3.tgz#25e37325fc6333e7ca58d4585385dd88911b07ce" - integrity sha512-/kyr5CL4XFJpMj9CvW8K1NNNqkzyOhxc7ibXhykiPyPiGOwO/ZbqnfDhqVx3JMSjOASeW1e6UlGNjnfTPvFkGQ== - -"@parcel/rust@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/rust/-/rust-2.16.3.tgz#74136baf6507e9f924b00a145637cfaf31636753" - integrity sha512-pUsgURnDdlHA9AqvEcm124/9+DB7GM7Mk0qQ9XDNiznl09n8XZ67lf/IIvaMW7y0vQ7FpTzRIrRzAJhGyMRbMw== +"@parcel/rust-darwin-arm64@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-darwin-arm64/-/rust-darwin-arm64-2.16.4.tgz#13f0a1327c9e02ae27de18db6143b47177e29264" + integrity sha512-P3Se36H9EO1fOlwXqQNQ+RsVKTGn5ztRSUGbLcT8ba6oOMmU1w7J4R810GgsCbwCuF10TJNUMkuD3Q2Sz15Q3Q== + +"@parcel/rust-darwin-x64@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-darwin-x64/-/rust-darwin-x64-2.16.4.tgz#e3b5df8b438dbc865110a38546ef0b5cacb89785" + integrity sha512-8aNKNyPIx3EthYpmVJevIdHmFsOApXAEYGi3HU69jTxLgSIfyEHDdGE9lEsMvhSrd/SSo4/euAtiV+pqK04wnA== + +"@parcel/rust-linux-arm-gnueabihf@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-linux-arm-gnueabihf/-/rust-linux-arm-gnueabihf-2.16.4.tgz#d315b4c88332739325bf1841662f4b7e6344e872" + integrity sha512-QrvqiSHaWRLc0JBHgUHVvDthfWSkA6AFN+ikV1UGENv4j2r/QgvuwJiG0VHrsL6pH5dRqj0vvngHzEgguke9DA== + +"@parcel/rust-linux-arm64-gnu@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-linux-arm64-gnu/-/rust-linux-arm64-gnu-2.16.4.tgz#64d23137e1578d559a615f944149cf35815e956e" + integrity sha512-f3gBWQHLHRUajNZi3SMmDQiEx54RoRbXtZYQNuBQy7+NolfFcgb1ik3QhkT7xovuTF/LBmaqP3UFy0PxvR/iwQ== + +"@parcel/rust-linux-arm64-musl@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-linux-arm64-musl/-/rust-linux-arm64-musl-2.16.4.tgz#c3396df8434da0e32cc58e18063ce31f5fd80f79" + integrity sha512-cwml18RNKsBwHyZnrZg4jpecXkWjaY/mCArocWUxkFXjjB97L56QWQM9W86f2/Y3HcFcnIGJwx1SDDKJrV6OIA== + +"@parcel/rust-linux-x64-gnu@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-linux-x64-gnu/-/rust-linux-x64-gnu-2.16.4.tgz#546ca77267717077a4e92364b9d688731a657193" + integrity sha512-0xIjQaN8hiG0F9R8coPYidHslDIrbfOS/qFy5GJNbGA3S49h61wZRBMQqa7JFW4+2T8R0J9j0SKHhLXpbLXrIg== + +"@parcel/rust-linux-x64-musl@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-linux-x64-musl/-/rust-linux-x64-musl-2.16.4.tgz#d1db7d43b0527c5dbc707e2576760e5161204937" + integrity sha512-fYn21GIecHK9RoZPKwT9NOwxwl3Gy3RYPR6zvsUi0+hpFo19Ph9EzFXN3lT8Pi5KiwQMCU4rsLb5HoWOBM1FeA== + +"@parcel/rust-win32-x64-msvc@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust-win32-x64-msvc/-/rust-win32-x64-msvc-2.16.4.tgz#998c2252341a6b0a2006e4bc056f4f27f9a8107f" + integrity sha512-TcpWC3I1mJpfP2++018lgvM7UX0P8IrzNxceBTHUKEIDMwmAYrUKAQFiaU0j1Ldqk6yP8SPZD3cvphumsYpJOQ== + +"@parcel/rust@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/rust/-/rust-2.16.4.tgz#a5b3098bd740555f39487050ac5bd4ee6a846d27" + integrity sha512-RBMKt9rCdv6jr4vXG6LmHtxzO5TuhQvXo1kSoSIF7fURRZ81D1jzBtLxwLmfxCPsofJNqWwdhy5vIvisX+TLlQ== optionalDependencies: - "@parcel/rust-darwin-arm64" "2.16.3" - "@parcel/rust-darwin-x64" "2.16.3" - "@parcel/rust-linux-arm-gnueabihf" "2.16.3" - "@parcel/rust-linux-arm64-gnu" "2.16.3" - "@parcel/rust-linux-arm64-musl" "2.16.3" - "@parcel/rust-linux-x64-gnu" "2.16.3" - "@parcel/rust-linux-x64-musl" "2.16.3" - "@parcel/rust-win32-x64-msvc" "2.16.3" + "@parcel/rust-darwin-arm64" "2.16.4" + "@parcel/rust-darwin-x64" "2.16.4" + "@parcel/rust-linux-arm-gnueabihf" "2.16.4" + "@parcel/rust-linux-arm64-gnu" "2.16.4" + "@parcel/rust-linux-arm64-musl" "2.16.4" + "@parcel/rust-linux-x64-gnu" "2.16.4" + "@parcel/rust-linux-x64-musl" "2.16.4" + "@parcel/rust-win32-x64-msvc" "2.16.4" "@parcel/source-map@^2.1.1": version "2.1.1" @@ -943,167 +950,167 @@ dependencies: detect-libc "^1.0.3" -"@parcel/transformer-babel@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.16.3.tgz#623f1261c654b5177a4d6b9c8b13feaf33b70986" - integrity sha512-Jsusa2xWlgrmBYmvuC70/SIvcNdYZj3NyQhCxTOARV2scksSKH8iSvNsMKepYiZl6nHRNOmnGOShz9xJqNpUDw== +"@parcel/transformer-babel@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.16.4.tgz#223c808aefe8246b922c69f1d441bbe4b9c0439e" + integrity sha512-CMDUOQYX7+cmeyHxHSFnoPcwvXNL7rRFE+Q06uVFzsYYiVhbwGF/1J5Bx4cW3Froumqla4YTytTsEteJEybkdA== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/utils" "2.16.3" + "@parcel/utils" "2.16.4" browserslist "^4.24.5" json5 "^2.2.3" nullthrows "^1.1.1" semver "^7.7.1" -"@parcel/transformer-css@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.16.3.tgz#dd4f7b5c5835a8283fcae3faa694fd9d09be0e4d" - integrity sha512-RKGfjvQQVYpd27Ag7QHzBEjqfN/hj6Yf6IlbUdOp06bo+XOXQXe5/n2ulJ1EL9ZjyDOtXbB94A7QzSQmtFGEow== +"@parcel/transformer-css@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.16.4.tgz#d3f76a5fae8691eaa635d87465d867dd2bd90043" + integrity sha512-VG/+DbDci2HKe20GFRDs65ZQf5GUFfnmZAa1BhVl/MO+ijT3XC3eoVUy5cExRkq4VLcPY4ytL0g/1T2D6x7lBQ== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/utils" "2.16.3" + "@parcel/utils" "2.16.4" browserslist "^4.24.5" lightningcss "^1.30.1" nullthrows "^1.1.1" -"@parcel/transformer-html@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.16.3.tgz#d8609fdcd6e8090356ef6e4a9e8f33948007b52b" - integrity sha512-j/f+fR3hS9g3Kw4mySyF2sN4mp0t6amq3x52SAptpa4C7w8XVWproc+3ZLgjzi91OPqNeQAQUNQMy86AfuMuEw== +"@parcel/transformer-html@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.16.4.tgz#bae5a32f42587fc6fc7114c61d4f0d7523545e04" + integrity sha512-w6JErYTeNS+KAzUAER18NHFIFFvxiLGd4Fht1UYcb/FDjJdLAMB/FljyEs0Rto/WAhZ2D0MuSL25HQh837R62g== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" -"@parcel/transformer-image@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.16.3.tgz#af0b2177e500d9e01e400af3e9ddb46a42bfdb23" - integrity sha512-q8BhaGSaGtIP1JPxDpRoRxs5Oa17sVR4c0kyPyxwP0QoihKth1eQElbINx+7Ikbt7LoGucPUKEsnxrDzkUt8og== +"@parcel/transformer-image@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.16.4.tgz#1f7b6dc37acdec4d2c1eed106bb846263d9ce3d8" + integrity sha512-ZzIn3KvvRqMfcect4Dy+57C9XoQXZhpVJKBdQWMp9wM1qJEgsVgGDcaSBYCs/UYSKMRMP6Wm20pKCt408RkQzg== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/utils" "2.16.3" - "@parcel/workers" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/utils" "2.16.4" + "@parcel/workers" "2.16.4" nullthrows "^1.1.1" -"@parcel/transformer-inline-string@^2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-inline-string/-/transformer-inline-string-2.16.3.tgz#f081e4079878fd0a4f10792ec58961ca2af4c960" - integrity sha512-oMjqqf/TfYQScy66ODEHWMInQYxpnlEpJDysT/OH4m+5+Q//Oh1hd6dl2g4SPmXUbgA9Y89ZfuifakoADpiCMg== +"@parcel/transformer-inline-string@^2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-inline-string/-/transformer-inline-string-2.16.4.tgz#c16786e23499bcf2f3beaeaf583e46d22d423c83" + integrity sha512-Kr5YZbN3xeb3asvle9+Nso/Tqxy0I/mdV2oQC30NuVVP+PXbOD4fC24QtORyt0V1UX93KqgmXKl7G75767FcVg== dependencies: - "@parcel/plugin" "2.16.3" + "@parcel/plugin" "2.16.4" -"@parcel/transformer-js@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.16.3.tgz#c7b0b8d06b5efdec0277beca7471b60ac25fdae3" - integrity sha512-k83yElHagwDRYfza7BrADdf9NRGpizX3zOfctfEsQWh9mEZLNJENivP6ZLB9Aje9H0GaaSTiYU8VwOWLXbLgOw== +"@parcel/transformer-js@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.16.4.tgz#91863d05668efc60d671acbf50161dee7210c468" + integrity sha512-FD2fdO6URwAGBPidb3x1dDgLBt972mko0LelcSU05aC/pcKaV9mbCtINbPul1MlStzkxDelhuImcCYIyerheVQ== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" "@parcel/source-map" "^2.1.1" - "@parcel/utils" "2.16.3" - "@parcel/workers" "2.16.3" + "@parcel/utils" "2.16.4" + "@parcel/workers" "2.16.4" "@swc/helpers" "^0.5.0" browserslist "^4.24.5" nullthrows "^1.1.1" regenerator-runtime "^0.14.1" semver "^7.7.1" -"@parcel/transformer-json@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.16.3.tgz#9f2e0f311a4a7e5bfcf8514f78baea0d05d8e362" - integrity sha512-iT4IKGT95+S/7RBK1MUY/KxD8ad9FUlElF+w40NBLv4lm012wkYogFRhEHnyElPOByZL1aJ8GaVOGbZL9yuZfg== +"@parcel/transformer-json@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.16.4.tgz#ae4d3e58fab6b574a6b07fb0b8365d798bcc1af4" + integrity sha512-pB3ZNqgokdkBCJ+4G0BrPYcIkyM9K1HVk0GvjzcLEFDKsoAp8BGEM68FzagFM/nVq9anYTshIaoh349GK0M/bg== dependencies: - "@parcel/plugin" "2.16.3" + "@parcel/plugin" "2.16.4" json5 "^2.2.3" -"@parcel/transformer-node@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-node/-/transformer-node-2.16.3.tgz#3bdb74d6f30019aec36df34a8e96183fd614b021" - integrity sha512-FIbSphLisxmzwqE43ALsGeSPSYBA3ZE6xmhAIgwoFdeI6VfTSkCZnGhSqUhP3m9R55IuWm/+NP6BlePWADmkwg== +"@parcel/transformer-node@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-node/-/transformer-node-2.16.4.tgz#87fab8af1998cd99f02b50233b815b8ee9f00c70" + integrity sha512-7t43CPGfMJk1LqFokwxHSsRi+kKC2QvDXaMtqiMShmk50LCwn81WgzuFvNhMwf6lSiBihWupGwF3Fqksg+aisg== dependencies: - "@parcel/plugin" "2.16.3" + "@parcel/plugin" "2.16.4" -"@parcel/transformer-postcss@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.16.3.tgz#0d159b4b084889bdf03d5595ceeffca12421d9f1" - integrity sha512-OMjU17OwPhPBK2LIzqQozBezolqI8jPgoT+CmoOkKr1GlgWMzCcHFpW6KQZxVVR+vI0lUEJp+RZc9MzhNndv4A== +"@parcel/transformer-postcss@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.16.4.tgz#70b3cdc5d0a27bb48c5b5dcee2157483a26dc6ff" + integrity sha512-jfmh9ho03H+qwz9S1b/a/oaOmgfMovtHKYDweIGMjKULKIee3AFRqo8RZIOuUMjDuqHWK8SqQmjery4syFV3Xw== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" + "@parcel/utils" "2.16.4" clone "^2.1.2" nullthrows "^1.1.1" postcss-value-parser "^4.2.0" semver "^7.7.1" -"@parcel/transformer-posthtml@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.16.3.tgz#2ad23334de71ecafdbb58ee5f2cc5fab8af5e8cc" - integrity sha512-y3iuM+yp8nPbt8sbQayPGR0saVGR6uj0aYr7hWoS0oUe9vZsH1mP3BTP6L6ABe/dZKU3QcFmMQgLwH6WC/apAA== +"@parcel/transformer-posthtml@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.16.4.tgz#153428a26add6720df1863655b3b66019fdf5a61" + integrity sha512-+GXsmGx1L25KQGQnwclgEuQe1t4QU+IoDkgN+Ikj+EnQCOWG4/ts2VpMBeqP5F18ZT4cCSRafj6317o/2lSGJg== dependencies: - "@parcel/plugin" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/plugin" "2.16.4" + "@parcel/utils" "2.16.4" -"@parcel/transformer-raw@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.16.3.tgz#440f623d0ba7b7a9cdb401942c42a42ae8508a2e" - integrity sha512-Lha1+z75QbNAsxMAffp5K+ykGXEYSNOFUqI/8XtetYfuqIvS5s/OBkwsg8MWbjtPkbKo1F3EwNBaIAagw/BbIg== +"@parcel/transformer-raw@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.16.4.tgz#02152b849133997833274b7a9fcba2172fbe7b8a" + integrity sha512-7WDUPq+bW11G9jKxaQIVL+NPGolV99oq/GXhpjYip0SaGaLzRCW7gEk60cftuk0O7MsDaX5jcAJm3G/AX+LJKg== dependencies: - "@parcel/plugin" "2.16.3" + "@parcel/plugin" "2.16.4" -"@parcel/transformer-react-refresh-wrap@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.16.3.tgz#69e3073eab78e11820bd330f711482381a539a3b" - integrity sha512-8rzO5iKF5bYrPUnbw4At0H7AwE+UHkuNNo385JL0VzXggrA0VsXsjjJwXVyhSeMvEbo2ioo/+nYUlazTQBABwA== +"@parcel/transformer-react-refresh-wrap@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.16.4.tgz#a1e876072e5f14e03b1c1791dc89ee1b8742eb22" + integrity sha512-MiLNZrsGQJTANKKa4lzZyUbGj/en0Hms474mMdQkCBFg6GmjfmXwaMMgtTfPA3ZwSp2+3LeObCyca/f9B2gBZQ== dependencies: - "@parcel/error-overlay" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/error-overlay" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/utils" "2.16.4" react-refresh "^0.16.0" -"@parcel/transformer-svg@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.16.3.tgz#a28f94b0cfbf85f202eec5fc68c01404df30a331" - integrity sha512-fDpUWSBZxt/R5pZUNd4gV/BX0c7B074lw/wmqwowjcwQU/QxhzPJBDlAsyTvOJ75PeJiQf/qFtnIK5bNwMoasA== +"@parcel/transformer-svg@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.16.4.tgz#888840d106fb92a6a6145ce618bf47427e803944" + integrity sha512-0dm4cQr/WpfQP6N0xjFtwdLTxcONDfoLgTOMk4eNUWydHipSgmLtvUk/nOc/FWkwztRScfAObtZXOiPOd3Oy9A== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/plugin" "2.16.3" - "@parcel/rust" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/plugin" "2.16.4" + "@parcel/rust" "2.16.4" -"@parcel/types-internal@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/types-internal/-/types-internal-2.16.3.tgz#f9b414ced018ae7ed0a0c4290c6d0eb275b2cf44" - integrity sha512-zi2GKdJHpNeW9sspTBfM68A9lekEztTWU8Dxs1ouPk90lfA0tfrMznAvkD5iJdKsM6usbgcqjjI8s+Ow8OrsBg== +"@parcel/types-internal@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/types-internal/-/types-internal-2.16.4.tgz#ea87e092f2ced03adb0c3e8c1e445e39bb325409" + integrity sha512-PE6Qmt5cjzBxX+6MPLiF7r+twoC+V9Skt3zyuBQ+H1c0i9o07Bbz2NKX10nvlPukfmW6Fu/1RvTLkzBZR1bU6A== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/feature-flags" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/feature-flags" "2.16.4" "@parcel/source-map" "^2.1.1" utility-types "^3.11.0" -"@parcel/types@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.16.3.tgz#9feeeba9a06f7be8b94c471a4da5fa5b7e65cf48" - integrity sha512-aIJJFMif/A7u86UEt3sJPZ/F7suQW56ugiCp2Y2mYTPHpTJbI2Knk9yO4fkWHNO1BrH6a/VUWh7bWIOsQtzL1Q== +"@parcel/types@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.16.4.tgz#e8a3d5f4404134763c4c240dd7bfeab2033be726" + integrity sha512-ctx4mBskZHXeDVHg4OjMwx18jfYH9BzI/7yqbDQVGvd5lyA+/oVVzYdpele2J2i2sSaJ87cA8nb57GDQ8kHAqA== dependencies: - "@parcel/types-internal" "2.16.3" - "@parcel/workers" "2.16.3" + "@parcel/types-internal" "2.16.4" + "@parcel/workers" "2.16.4" -"@parcel/utils@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.16.3.tgz#6f175c184e337d38d9735708c0bb1268e8df94ee" - integrity sha512-g/yqVWSdZqPvTiS96dEK9MEl7q6w31u+luD5VGt6f9w6PQCpuVajhhDNuXf9uzDU/dL4sSZPKUhLteVZDqryHA== +"@parcel/utils@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.16.4.tgz#d80a713358d10de7c3b37273aad499cbc051d540" + integrity sha512-lkmxQHcHyOWZLbV8t+h2CGZIkPiBurLm/TS5wNT7+tq0qt9KbVwL7FP2K93TbXhLMGTmpI79Bf3qKniPM167Mw== dependencies: - "@parcel/codeframe" "2.16.3" - "@parcel/diagnostic" "2.16.3" - "@parcel/logger" "2.16.3" - "@parcel/markdown-ansi" "2.16.3" - "@parcel/rust" "2.16.3" + "@parcel/codeframe" "2.16.4" + "@parcel/diagnostic" "2.16.4" + "@parcel/logger" "2.16.4" + "@parcel/markdown-ansi" "2.16.4" + "@parcel/rust" "2.16.4" "@parcel/source-map" "^2.1.1" chalk "^4.1.2" nullthrows "^1.1.1" @@ -1197,16 +1204,16 @@ "@parcel/watcher-win32-ia32" "2.5.1" "@parcel/watcher-win32-x64" "2.5.1" -"@parcel/workers@2.16.3": - version "2.16.3" - resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.16.3.tgz#3ef399d2bd643f61d4344756c5f3dfb41c8b727c" - integrity sha512-SxIXRnrlQFhw377wxWC5WIl1FL1Y9IedhUtuc7j3uac3tlbCQJJ+3rFr5/BDUknJbTktvVsPakE98fH7TIJyyw== +"@parcel/workers@2.16.4": + version "2.16.4" + resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.16.4.tgz#80a6be911598cf42874af6041aec0bc1874f7305" + integrity sha512-dkBEWqnHXDZnRbTZouNt4uEGIslJT+V0c8OH1MPOfjISp1ucD6/u9ET8k9d/PxS9h1hL53og0SpBuuSEPLDl6A== dependencies: - "@parcel/diagnostic" "2.16.3" - "@parcel/logger" "2.16.3" - "@parcel/profiler" "2.16.3" - "@parcel/types-internal" "2.16.3" - "@parcel/utils" "2.16.3" + "@parcel/diagnostic" "2.16.4" + "@parcel/logger" "2.16.4" + "@parcel/profiler" "2.16.4" + "@parcel/types-internal" "2.16.4" + "@parcel/utils" "2.16.4" nullthrows "^1.1.1" "@pkgjs/parseargs@^0.11.0": @@ -2104,7 +2111,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" -css-tree@^3.1.0: +css-tree@^3.0.0, css-tree@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.1.0.tgz#7aabc035f4e66b5c86f54570d55e05b1346eb0fd" integrity sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w== @@ -2112,22 +2119,23 @@ css-tree@^3.1.0: mdn-data "2.12.2" source-map-js "^1.0.1" -cssstyle@^5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-5.3.4.tgz#75635973c06998f36b3224cfc4b11b045e224f75" - integrity sha512-KyOS/kJMEq5O9GdPnaf82noigg5X5DYn0kZPJTaAsCUaBizp6Xa1y9D4Qoqf/JazEXWuruErHgVXwjN5391ZJw== +cssstyle@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-6.0.1.tgz#ca992a9dc7118d2a22adef96d01a1979c23ac779" + integrity sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog== dependencies: - "@asamuzakjp/css-color" "^4.1.0" - "@csstools/css-syntax-patches-for-csstree" "1.0.14" + "@asamuzakjp/css-color" "^4.1.2" + "@csstools/css-syntax-patches-for-csstree" "^1.0.26" css-tree "^3.1.0" + lru-cache "^11.2.5" -data-urls@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-6.0.0.tgz#95a7943c8ac14c1d563b771f2621cc50e8ec7744" - integrity sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA== +data-urls@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-7.0.0.tgz#6dce8b63226a1ecfdd907ce18a8ccfb1eee506d3" + integrity sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA== dependencies: - whatwg-mimetype "^4.0.0" - whatwg-url "^15.0.0" + whatwg-mimetype "^5.0.0" + whatwg-url "^16.0.0" debounce@^1.2.0: version "1.2.1" @@ -2983,16 +2991,17 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsdom@^27.4.0: - version "27.4.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-27.4.0.tgz#c36af2e43e1281a7e8bb8f255086435d177801f2" - integrity sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ== - dependencies: - "@acemir/cssom" "^0.9.28" - "@asamuzakjp/dom-selector" "^6.7.6" - "@exodus/bytes" "^1.6.0" - cssstyle "^5.3.4" - data-urls "^6.0.0" +jsdom@^28.1.0: + version "28.1.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-28.1.0.tgz#ac4203e58fd24d7b0f34359ab00d6d9caebd4b62" + integrity sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug== + dependencies: + "@acemir/cssom" "^0.9.31" + "@asamuzakjp/dom-selector" "^6.8.1" + "@bramus/specificity" "^2.4.2" + "@exodus/bytes" "^1.11.0" + cssstyle "^6.0.1" + data-urls "^7.0.0" decimal.js "^10.6.0" html-encoding-sniffer "^6.0.0" http-proxy-agent "^7.0.2" @@ -3002,11 +3011,11 @@ jsdom@^27.4.0: saxes "^6.0.0" symbol-tree "^3.2.4" tough-cookie "^6.0.0" + undici "^7.21.0" w3c-xmlserializer "^5.0.0" - webidl-conversions "^8.0.0" - whatwg-mimetype "^4.0.0" - whatwg-url "^15.1.0" - ws "^8.18.3" + webidl-conversions "^8.0.1" + whatwg-mimetype "^5.0.0" + whatwg-url "^16.0.0" xml-name-validator "^5.0.0" jsesc@^3.0.2: @@ -3250,15 +3259,10 @@ lru-cache@^10.2.0: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== -lru-cache@^11.2.2: - version "11.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.2.tgz#40fd37edffcfae4b2940379c0722dc6eeaa75f24" - integrity sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg== - -lru-cache@^11.2.4: - version "11.2.4" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.4.tgz#ecb523ebb0e6f4d837c807ad1abaea8e0619770d" - integrity sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg== +lru-cache@^11.2.5, lru-cache@^11.2.6: + version "11.2.6" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.6.tgz#356bf8a29e88a7a2945507b31f6429a65a192c58" + integrity sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ== lru-cache@^5.1.1: version "5.1.1" @@ -3658,23 +3662,23 @@ package-json-from-dist@^1.0.0: resolved "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== -parcel@^2.16.3: - version "2.16.3" - resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.16.3.tgz#6dc495713138d8f748ced14b8da77b3825e0491a" - integrity sha512-N9jnwcTeVEaRjjJCCHmYfPCvjjJeHZuuO50qL4CCNcQX4RjwPuOaDft7hvTT2W8PIb4XhhZKDYB1lstZhXLJRQ== - dependencies: - "@parcel/config-default" "2.16.3" - "@parcel/core" "2.16.3" - "@parcel/diagnostic" "2.16.3" - "@parcel/events" "2.16.3" - "@parcel/feature-flags" "2.16.3" - "@parcel/fs" "2.16.3" - "@parcel/logger" "2.16.3" - "@parcel/package-manager" "2.16.3" - "@parcel/reporter-cli" "2.16.3" - "@parcel/reporter-dev-server" "2.16.3" - "@parcel/reporter-tracer" "2.16.3" - "@parcel/utils" "2.16.3" +parcel@^2.16.4: + version "2.16.4" + resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.16.4.tgz#a9219e35b97163e4fbee47241d641ed23c48c800" + integrity sha512-RQlrqs4ujYNJpTQi+dITqPKNhRWEqpjPd1YBcGp50Wy3FcJHpwu0/iRm7XWz2dKU/Bwp2qCcVYPIeEDYi2uOUw== + dependencies: + "@parcel/config-default" "2.16.4" + "@parcel/core" "2.16.4" + "@parcel/diagnostic" "2.16.4" + "@parcel/events" "2.16.4" + "@parcel/feature-flags" "2.16.4" + "@parcel/fs" "2.16.4" + "@parcel/logger" "2.16.4" + "@parcel/package-manager" "2.16.4" + "@parcel/reporter-cli" "2.16.4" + "@parcel/reporter-dev-server" "2.16.4" + "@parcel/reporter-tracer" "2.16.4" + "@parcel/utils" "2.16.4" chalk "^4.1.2" commander "^12.1.0" get-port "^4.2.0" @@ -3775,10 +3779,10 @@ prettier-plugin-organize-imports@^4.3.0: resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz#e8d392e2040b3e5fc1476967d669487dde0eea76" integrity sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw== -prettier@3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.0.tgz#f72cf71505133f40cfa2ef77a2668cdc558fcd69" - integrity sha512-yEPsovQfpxYfgWNhCfECjG5AQaO+K3dp6XERmOepyPDVqcJm+bjyCVO3pmU+nAPe0N5dDvekfGezt/EIiRe1TA== +prettier@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== pretty-format@^27.0.2: version "27.5.1" @@ -3811,9 +3815,9 @@ punycode@^2.3.1: integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== qs@^6.5.2: - version "6.14.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" - integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== + version "6.14.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.2.tgz#b5634cf9d9ad9898e31fba3504e866e8efb6798c" + integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== dependencies: side-channel "^1.1.0" @@ -4349,6 +4353,11 @@ undici-types@~7.8.0: resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz" integrity sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== +undici@^7.21.0: + version "7.22.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.22.0.tgz#7a82590a5908e504a47d85c60b0f89ca14240e60" + integrity sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg== + unpipe@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" @@ -4394,23 +4403,24 @@ weak-lru-cache@^1.2.2: resolved "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz" integrity sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw== -webidl-conversions@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-8.0.0.tgz#821c92aa4f88d88a31264d887e244cb9655690c6" - integrity sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA== +webidl-conversions@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-8.0.1.tgz#0657e571fe6f06fcb15ca50ed1fdbcb495cd1686" + integrity sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ== -whatwg-mimetype@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz" - integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== +whatwg-mimetype@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz#d8232895dbd527ceaee74efd4162008fb8a8cf48" + integrity sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw== -whatwg-url@^15.0.0, whatwg-url@^15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-15.1.0.tgz#5c433439b9a5789eeb3806bbd0da89a8bd40b8d7" - integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g== +whatwg-url@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-16.0.0.tgz#73bafd41b77c54abfa40c4a4fd9c6103024e97d2" + integrity sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ== dependencies: + "@exodus/bytes" "^1.11.0" tr46 "^6.0.0" - webidl-conversions "^8.0.0" + webidl-conversions "^8.0.1" which-module@^2.0.0: version "2.0.1" @@ -4485,11 +4495,6 @@ ws@^7.5.10: resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.18.3: - version "8.18.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472" - integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== - xml-name-validator@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz"